From 22cd27b2bbe9ab1ffa7ef06fa764b5147ae17dad Mon Sep 17 00:00:00 2001 From: Pierre Date: Sun, 10 Jun 2012 10:17:01 +0200 Subject: [PATCH 1/1] first import --- HTML.py | 493 + HTML/HTML.py | 493 + HTML/HTML.py.html | 206 + HTML/HTML_tutorial.py | 208 + HTML/Licence_CeCILL_V2-en.html | 1002 + HTML/Licence_CeCILL_V2-fr.html | 968 + HTML/PKG-INFO | 20 + HTML/README.txt | 29 + HTML/setup.py | 41 + INSTALL_WINDOWS | 22 + KeyFrame.py | 160 + Liste.py | 315 + OptionAlceste.py | 306 + PrintRScript.py | 622 + ProfList.py | 875 + Rscripts/CHD.R | 389 + Rscripts/CHD.R.old | 245 + Rscripts/CHDKmeans.R | 471 + Rscripts/CHDPOND.R | 455 + Rscripts/Rfunct.R | 51 + Rscripts/Rgraph.R | 575 + Rscripts/Rprof.out | 1 + Rscripts/afc.R | 65 + Rscripts/afc_graph.R | 223 + Rscripts/afc_lex.R | 33 + Rscripts/anacor.R | 104 + Rscripts/association.R | 65 + Rscripts/chd.R | 87 + Rscripts/chdfunct.R | 622 + Rscripts/chdquest.R | 324 + Rscripts/chdtxt.R | 318 + Rscripts/graph.txt | 148 + Rscripts/multipam.R | 121 + Rscripts/mysvd.R | 42 + Rscripts/pamtxt.R | 29 + Rscripts/plotafcm.R | 208 + Rscripts/simi.R | 297 + Rscripts/simied.R | 311 + Rscripts/simiold.R | 502 + Rscripts/splitafc.R | 77 + TODO | 13 + agw/__init__.py | 121 + agw/__init__.pyc | Bin 0 -> 6117 bytes agw/aui/__init__.py | 290 + agw/aui/__init__.pyc | Bin 0 -> 15993 bytes agw/aui/aui_constants.py | 2588 + agw/aui/aui_constants.pyc | Bin 0 -> 127728 bytes agw/aui/aui_switcherdialog.py | 1216 + agw/aui/aui_utilities.py | 678 + agw/aui/aui_utilities.pyc | Bin 0 -> 19758 bytes agw/aui/auibar.py | 3926 + agw/aui/auibar.pyc | Bin 0 -> 115949 bytes agw/aui/auibook.py | 5736 + agw/aui/auibook.pyc | Bin 0 -> 149599 bytes agw/aui/dockart.py | 1162 + agw/aui/dockart.pyc | Bin 0 -> 35127 bytes agw/aui/framemanager.py | 10385 ++ agw/aui/framemanager.pyc | Bin 0 -> 279848 bytes agw/aui/tabart.py | 2777 + agw/aui/tabart.pyc | Bin 0 -> 74640 bytes agw/aui/tabmdi.py | 666 + agw/aui/tabmdi.pyc | Bin 0 -> 18538 bytes analysetxt.py | 258 + app.fil | 37 + bestsvg.py | 26 + build_dictionnaries.py | 190 + cable.py | 62 + checkinstall.py | 194 + checkversion.py | 49 + chemins.py | 278 + colors.py | 31 + configuration/.corpus.cfg.swo | Bin 0 -> 12288 bytes configuration/.corpus.cfg.swp | Bin 0 -> 12288 bytes configuration/CHD.cfg | 27 + configuration/alceste.cfg | 32 + configuration/corpus.cfg | 24 + configuration/global.cfg | 11 + configuration/iramuteq.cfg | 12 + configuration/key.cfg | 28 + configuration/pam.cfg | 20 + configuration/path.cfg | 4 + corpus.py | 1278 + corpusNG.py | 965 + debian/README | 6 + debian/README.Debian | 6 + debian/changelog | 5 + debian/compat | 1 + debian/control | 12 + debian/copyright | 38 + debian/dirs | 4 + debian/docs | 3 + debian/files | 1 + debian/iramuteq.debhelper.log | 14 + debian/rules | 112 + dialog.py | 3011 + dictionnaires/About | 1 + dictionnaires/expression.txt.back | 4613 + dictionnaires/expression.txt.old | 4608 + dictionnaires/expression_de.txt | 8 + dictionnaires/expression_en.txt | 47 + dictionnaires/expression_fr.txt | 4596 + dictionnaires/expression_it.txt | 298 + dictionnaires/lexique.txt.back | 125754 ++++++++++++++++ dictionnaires/lexique.txt.old | 125749 ++++++++++++++++ dictionnaires/lexique_de.txt | 737 + dictionnaires/lexique_en.txt | 97893 +++++++++++++ dictionnaires/lexique_fr.txt | 125721 ++++++++++++++++ dictionnaires/lexique_it.txt | 167966 ++++++++++++++++++++++ dictionnaires/lexiqueac.txt | 125754 ++++++++++++++++ documentation/Warning_icon.png | Bin 0 -> 1692 bytes documentation/documentation-img1.png | Bin 0 -> 1692 bytes documentation/documentation-img2.png | Bin 0 -> 1692 bytes documentation/documentation.doc | Bin 0 -> 443392 bytes documentation/documentation.html | 1263 + documentation/documentation.odt | Bin 0 -> 317157 bytes documentation/documentation.pdf | Bin 0 -> 266019 bytes documentation/documentation.txt | 142 + documentation/documentation_cln.doc | Bin 0 -> 437760 bytes documentation/documentation_html_m443ae5e9.png | Bin 0 -> 1692 bytes documentation/html/documentation.html | 359 + documentation/similitude.txt | 491 + documentation/test-wiki | 93 + extract.py | 73 + formes.db | Bin 0 -> 3072 bytes functions.py | 580 + gpl-2.0-fr.txt | 127 + gpl-2.0.txt | 339 + guifunct.py | 113 + guiparam3d.py | 130 + images/LexTexte4.jpg | Bin 0 -> 12255 bytes images/Rlogo.jpg | Bin 0 -> 3173 bytes images/but_dendro.png | Bin 0 -> 691 bytes images/button_afc.jpg | Bin 0 -> 2884 bytes images/button_export.jpg | Bin 0 -> 1384 bytes images/button_export.jpg.png | Bin 0 -> 1044 bytes images/button_simi.jpg | Bin 0 -> 3391 bytes images/iraicone.ico | Bin 0 -> 58036 bytes images/iraicone.png | Bin 0 -> 4405 bytes images/iraicone.svg | 227 + images/iraicone16x16.png | Bin 0 -> 949 bytes images/iraicone248x248.png | Bin 0 -> 41317 bytes images/iraicone255x255.png | Bin 0 -> 42778 bytes images/iraicone32x32.png | Bin 0 -> 2548 bytes images/iraicone48x48.png | Bin 0 -> 4484 bytes images/python-logo.jpg | Bin 0 -> 8826 bytes images/splash.png | Bin 0 -> 91382 bytes images/splash.svg | 249 + images/svn-commit.tmp | 4 + iracmd.py | 129 + iramuteq | 2 + iramuteq.desktop | 8 + iramuteq.py | 1138 + iramuteq_en.po | 49 + iramuteq_fr_FR.po | 49 + iramuteqsvn.desktop | 8 + iraopen.py | 128 + layout.py | 858 + listlex.py | 376 + locale/en/LC_MESSAGES/iramuteq.mo | Bin 0 -> 367 bytes locale/fr_FR/LC_MESSAGES/iramuteq.mo | Bin 0 -> 752 bytes messages.pot | 49 + mki18n.py | 457 + newsimi.py | 23 + ooolib.py | 1962 + openanalyse.py | 137 + parse_factiva_html.py | 106 + parse_factiva_txt.py | 73 + parse_factiva_txt2.py | 77 + parse_factiva_xml.py | 171 + profile_segment.py | 151 + putcorpusindb.py | 72 + search_list.py | 357 + search_tools.py | 83 + setup.py | 25 + setup.py.win | 50 + sheet.py | 35 + son_fin.wav | Bin 0 -> 281182 bytes tabafcm.py | 105 + tabchdalc.py | 219 + tabchddist.py | 287 + tabchi2.py | 426 + tabfrequence.py | 227 + tableau.py | 325 + tabrsimple.py | 115 + tabsimi.py | 772 + tabstudent.py | 341 + tabverges.py | 54 + testdecoupe.txt | 8901 ++ textafcuci.py | 122 + textaslexico.py | 294 + textchdalc.py | 299 + textcheckcorpus.py | 65 + textclassechd.py | 38 + textdist.py | 202 + textsimi.py | 170 + textstat.py | 316 + textwordcloud.py | 182 + tree.py | 811 + ttparser.py | 252 + uces.db | Bin 0 -> 2048 bytes usecorpusNG.py | 28 + word_stat.py | 29 + 202 files changed, 856908 insertions(+) create mode 100644 HTML.py create mode 100644 HTML/HTML.py create mode 100644 HTML/HTML.py.html create mode 100644 HTML/HTML_tutorial.py create mode 100644 HTML/Licence_CeCILL_V2-en.html create mode 100644 HTML/Licence_CeCILL_V2-fr.html create mode 100644 HTML/PKG-INFO create mode 100644 HTML/README.txt create mode 100644 HTML/setup.py create mode 100644 INSTALL_WINDOWS create mode 100755 KeyFrame.py create mode 100644 Liste.py create mode 100755 OptionAlceste.py create mode 100644 PrintRScript.py create mode 100644 ProfList.py create mode 100644 Rscripts/CHD.R create mode 100644 Rscripts/CHD.R.old create mode 100644 Rscripts/CHDKmeans.R create mode 100644 Rscripts/CHDPOND.R create mode 100644 Rscripts/Rfunct.R create mode 100644 Rscripts/Rgraph.R create mode 100644 Rscripts/Rprof.out create mode 100644 Rscripts/afc.R create mode 100644 Rscripts/afc_graph.R create mode 100644 Rscripts/afc_lex.R create mode 100644 Rscripts/anacor.R create mode 100644 Rscripts/association.R create mode 100644 Rscripts/chd.R create mode 100644 Rscripts/chdfunct.R create mode 100644 Rscripts/chdquest.R create mode 100644 Rscripts/chdtxt.R create mode 100644 Rscripts/graph.txt create mode 100644 Rscripts/multipam.R create mode 100644 Rscripts/mysvd.R create mode 100644 Rscripts/pamtxt.R create mode 100644 Rscripts/plotafcm.R create mode 100644 Rscripts/simi.R create mode 100644 Rscripts/simied.R create mode 100644 Rscripts/simiold.R create mode 100644 Rscripts/splitafc.R create mode 100644 TODO create mode 100644 agw/__init__.py create mode 100644 agw/__init__.pyc create mode 100644 agw/aui/__init__.py create mode 100644 agw/aui/__init__.pyc create mode 100644 agw/aui/aui_constants.py create mode 100644 agw/aui/aui_constants.pyc create mode 100644 agw/aui/aui_switcherdialog.py create mode 100644 agw/aui/aui_utilities.py create mode 100644 agw/aui/aui_utilities.pyc create mode 100644 agw/aui/auibar.py create mode 100644 agw/aui/auibar.pyc create mode 100644 agw/aui/auibook.py create mode 100644 agw/aui/auibook.pyc create mode 100644 agw/aui/dockart.py create mode 100644 agw/aui/dockart.pyc create mode 100644 agw/aui/framemanager.py create mode 100644 agw/aui/framemanager.pyc create mode 100644 agw/aui/tabart.py create mode 100644 agw/aui/tabart.pyc create mode 100644 agw/aui/tabmdi.py create mode 100644 agw/aui/tabmdi.pyc create mode 100644 analysetxt.py create mode 100644 app.fil create mode 100644 bestsvg.py create mode 100644 build_dictionnaries.py create mode 100644 cable.py create mode 100644 checkinstall.py create mode 100644 checkversion.py create mode 100644 chemins.py create mode 100644 colors.py create mode 100644 configuration/.corpus.cfg.swo create mode 100644 configuration/.corpus.cfg.swp create mode 100644 configuration/CHD.cfg create mode 100644 configuration/alceste.cfg create mode 100644 configuration/corpus.cfg create mode 100644 configuration/global.cfg create mode 100644 configuration/iramuteq.cfg create mode 100644 configuration/key.cfg create mode 100644 configuration/pam.cfg create mode 100644 configuration/path.cfg create mode 100644 corpus.py create mode 100644 corpusNG.py create mode 100644 debian/README create mode 100644 debian/README.Debian create mode 100644 debian/changelog create mode 100644 debian/compat create mode 100644 debian/control create mode 100644 debian/copyright create mode 100644 debian/dirs create mode 100644 debian/docs create mode 100644 debian/files create mode 100644 debian/iramuteq.debhelper.log create mode 100755 debian/rules create mode 100755 dialog.py create mode 100644 dictionnaires/About create mode 100644 dictionnaires/expression.txt.back create mode 100644 dictionnaires/expression.txt.old create mode 100644 dictionnaires/expression_de.txt create mode 100644 dictionnaires/expression_en.txt create mode 100644 dictionnaires/expression_fr.txt create mode 100644 dictionnaires/expression_it.txt create mode 100644 dictionnaires/lexique.txt.back create mode 100644 dictionnaires/lexique.txt.old create mode 100644 dictionnaires/lexique_de.txt create mode 100644 dictionnaires/lexique_en.txt create mode 100644 dictionnaires/lexique_fr.txt create mode 100644 dictionnaires/lexique_it.txt create mode 100644 dictionnaires/lexiqueac.txt create mode 100644 documentation/Warning_icon.png create mode 100644 documentation/documentation-img1.png create mode 100644 documentation/documentation-img2.png create mode 100644 documentation/documentation.doc create mode 100644 documentation/documentation.html create mode 100644 documentation/documentation.odt create mode 100644 documentation/documentation.pdf create mode 100644 documentation/documentation.txt create mode 100644 documentation/documentation_cln.doc create mode 100644 documentation/documentation_html_m443ae5e9.png create mode 100644 documentation/html/documentation.html create mode 100644 documentation/similitude.txt create mode 100644 documentation/test-wiki create mode 100644 extract.py create mode 100644 formes.db create mode 100644 functions.py create mode 100644 gpl-2.0-fr.txt create mode 100644 gpl-2.0.txt create mode 100644 guifunct.py create mode 100755 guiparam3d.py create mode 100644 images/LexTexte4.jpg create mode 100644 images/Rlogo.jpg create mode 100644 images/but_dendro.png create mode 100644 images/button_afc.jpg create mode 100644 images/button_export.jpg create mode 100644 images/button_export.jpg.png create mode 100644 images/button_simi.jpg create mode 100644 images/iraicone.ico create mode 100644 images/iraicone.png create mode 100644 images/iraicone.svg create mode 100644 images/iraicone16x16.png create mode 100644 images/iraicone248x248.png create mode 100644 images/iraicone255x255.png create mode 100644 images/iraicone32x32.png create mode 100644 images/iraicone48x48.png create mode 100644 images/python-logo.jpg create mode 100644 images/splash.png create mode 100644 images/splash.svg create mode 100644 images/svn-commit.tmp create mode 100644 iracmd.py create mode 100755 iramuteq create mode 100644 iramuteq.desktop create mode 100644 iramuteq.py create mode 100644 iramuteq_en.po create mode 100644 iramuteq_fr_FR.po create mode 100644 iramuteqsvn.desktop create mode 100644 iraopen.py create mode 100644 layout.py create mode 100644 listlex.py create mode 100644 locale/en/LC_MESSAGES/iramuteq.mo create mode 100644 locale/fr_FR/LC_MESSAGES/iramuteq.mo create mode 100644 messages.pot create mode 100644 mki18n.py create mode 100644 newsimi.py create mode 100644 ooolib.py create mode 100644 openanalyse.py create mode 100644 parse_factiva_html.py create mode 100644 parse_factiva_txt.py create mode 100644 parse_factiva_txt2.py create mode 100644 parse_factiva_xml.py create mode 100644 profile_segment.py create mode 100644 putcorpusindb.py create mode 100644 search_list.py create mode 100644 search_tools.py create mode 100644 setup.py create mode 100644 setup.py.win create mode 100644 sheet.py create mode 100644 son_fin.wav create mode 100644 tabafcm.py create mode 100644 tabchdalc.py create mode 100644 tabchddist.py create mode 100755 tabchi2.py create mode 100644 tabfrequence.py create mode 100644 tableau.py create mode 100644 tabrsimple.py create mode 100644 tabsimi.py create mode 100644 tabstudent.py create mode 100644 tabverges.py create mode 100644 testdecoupe.txt create mode 100644 textafcuci.py create mode 100644 textaslexico.py create mode 100644 textchdalc.py create mode 100644 textcheckcorpus.py create mode 100644 textclassechd.py create mode 100644 textdist.py create mode 100644 textsimi.py create mode 100644 textstat.py create mode 100644 textwordcloud.py create mode 100644 tree.py create mode 100644 ttparser.py create mode 100644 uces.db create mode 100644 usecorpusNG.py create mode 100644 word_stat.py diff --git a/HTML.py b/HTML.py new file mode 100644 index 0000000..b03c0a2 --- /dev/null +++ b/HTML.py @@ -0,0 +1,493 @@ +#!/usr/bin/python +# -*- coding: iso-8859-1 -*- +""" +HTML.py - v0.04 2009-07-28 Philippe Lagadec + +This module provides a few classes to easily generate HTML code such as tables +and lists. + +Project website: http://www.decalage.info/python/html + +License: CeCILL (open-source GPL compatible), see source code for details. + http://www.cecill.info +""" + +__version__ = '0.04' +__date__ = '2009-07-28' +__author__ = 'Philippe Lagadec' + +#--- LICENSE ------------------------------------------------------------------ + +# Copyright Philippe Lagadec - see http://www.decalage.info/contact for contact info +# +# This module provides a few classes to easily generate HTML tables and lists. +# +# This software is governed by the CeCILL license under French law and +# abiding by the rules of distribution of free software. You can use, +# modify and/or redistribute the software under the terms of the CeCILL +# license as circulated by CEA, CNRS and INRIA at the following URL +# "http://www.cecill.info". +# +# A copy of the CeCILL license is also provided in these attached files: +# Licence_CeCILL_V2-en.html and Licence_CeCILL_V2-fr.html +# +# As a counterpart to the access to the source code and rights to copy, +# modify and redistribute granted by the license, users are provided only +# with a limited warranty and the software's author, the holder of the +# economic rights, and the successive licensors have only limited +# liability. +# +# In this respect, the user's attention is drawn to the risks associated +# with loading, using, modifying and/or developing or reproducing the +# software by the user in light of its specific status of free software, +# that may mean that it is complicated to manipulate, and that also +# therefore means that it is reserved for developers and experienced +# professionals having in-depth computer knowledge. Users are therefore +# encouraged to load and test the software's suitability as regards their +# requirements in conditions enabling the security of their systems and/or +# data to be ensured and, more generally, to use and operate it in the +# same conditions as regards security. +# +# The fact that you are presently reading this means that you have had +# knowledge of the CeCILL license and that you accept its terms. + + +#--- CHANGES ------------------------------------------------------------------ + +# 2008-10-06 v0.01 PL: - First version +# 2008-10-13 v0.02 PL: - added cellspacing and cellpadding to table +# - added functions to ease one-step creation of tables +# and lists +# 2009-07-21 v0.03 PL: - added column attributes and styles (first attempt) +# (thanks to an idea submitted by Michal Cernoevic) +# 2009-07-28 v0.04 PL: - improved column styles, workaround for Mozilla + + +#------------------------------------------------------------------------------- +#TODO: +# - method to return a generator (yield each row) instead of a single string +# - unicode support (input and output) +# - escape text in cells (optional) +# - constants for standard colors +# - use lxml to generate well-formed HTML ? +# - add classes/functions to generate a HTML page, paragraphs, headings, etc... + + +#--- THANKS -------------------------------------------------------------------- + +# - Michal Cernoevic, for the idea of column styles. + +#--- REFERENCES ---------------------------------------------------------------- + +# HTML 4.01 specs: http://www.w3.org/TR/html4/struct/tables.html + +# Colors: http://www.w3.org/TR/html4/types.html#type-color + +# Columns alignement and style, one of the oldest and trickiest bugs in Mozilla: +# https://bugzilla.mozilla.org/show_bug.cgi?id=915 + + +#--- CONSTANTS ----------------------------------------------------------------- + +# Table style to get thin black lines in Mozilla/Firefox instead of 3D borders +TABLE_STYLE_THINBORDER = "border: 1px solid #000000; border-collapse: collapse;" +#TABLE_STYLE_THINBORDER = "border: 1px solid #000000;" + + +#=== CLASSES =================================================================== + +class TableCell (object): + """ + a TableCell object is used to create a cell in a HTML table. (TD or TH) + + Attributes: + - text: text in the cell (may contain HTML tags). May be any object which + can be converted to a string using str(). + - header: bool, false for a normal data cell (TD), true for a header cell (TH) + - bgcolor: str, background color + - width: str, width + - align: str, horizontal alignement (left, center, right, justify or char) + - char: str, alignment character, decimal point if not specified + - charoff: str, see HTML specs + - valign: str, vertical alignment (top|middle|bottom|baseline) + - style: str, CSS style + - attribs: dict, additional attributes for the TD/TH tag + + Reference: http://www.w3.org/TR/html4/struct/tables.html#h-11.2.6 + """ + + def __init__(self, text="", bgcolor=None, header=False, width=None, + align=None, char=None, charoff=None, valign=None, style=None, + attribs=None): + """TableCell constructor""" + self.text = text + self.bgcolor = bgcolor + self.header = header + self.width = width + self.align = align + self.char = char + self.charoff = charoff + self.valign = valign + self.style = style + self.attribs = attribs + if attribs==None: + self.attribs = {} + + def __str__(self): + """return the HTML code for the table cell as a string""" + attribs_str = "" + if self.bgcolor: self.attribs['bgcolor'] = self.bgcolor + if self.width: self.attribs['width'] = self.width + if self.align: self.attribs['align'] = self.align + if self.char: self.attribs['char'] = self.char + if self.charoff: self.attribs['charoff'] = self.charoff + if self.valign: self.attribs['valign'] = self.valign + if self.style: self.attribs['style'] = self.style + for attr in self.attribs: + attribs_str += ' %s="%s"' % (attr, self.attribs[attr]) + if self.text: + text = str(self.text) + else: + # An empty cell should at least contain a non-breaking space + text = ' ' + if self.header: + return ' %s\n' % (attribs_str, text) + else: + return ' %s\n' % (attribs_str, text) + +#------------------------------------------------------------------------------- + +class TableRow (object): + """ + a TableRow object is used to create a row in a HTML table. (TR tag) + + Attributes: + - cells: list, tuple or any iterable, containing one string or TableCell + object for each cell + - header: bool, true for a header row (TH), false for a normal data row (TD) + - bgcolor: str, background color + - col_align, col_valign, col_char, col_charoff, col_styles: see Table class + - attribs: dict, additional attributes for the TR tag + + Reference: http://www.w3.org/TR/html4/struct/tables.html#h-11.2.5 + """ + + def __init__(self, cells=None, bgcolor=None, header=False, attribs=None, + col_align=None, col_valign=None, col_char=None, + col_charoff=None, col_styles=None): + """TableCell constructor""" + self.bgcolor = bgcolor + self.cells = cells + self.header = header + self.col_align = col_align + self.col_valign = col_valign + self.col_char = col_char + self.col_charoff = col_charoff + self.col_styles = col_styles + self.attribs = attribs + if attribs==None: + self.attribs = {} + + def __str__(self): + """return the HTML code for the table row as a string""" + attribs_str = "" + if self.bgcolor: self.attribs['bgcolor'] = self.bgcolor + for attr in self.attribs: + attribs_str += ' %s="%s"' % (attr, self.attribs[attr]) + result = ' \n' % attribs_str + for cell in self.cells: + col = self.cells.index(cell) # cell column index + if not isinstance(cell, TableCell): + cell = TableCell(cell, header=self.header) + # apply column alignment if specified: + if self.col_align and cell.align==None: + cell.align = self.col_align[col] + if self.col_char and cell.char==None: + cell.char = self.col_char[col] + if self.col_charoff and cell.charoff==None: + cell.charoff = self.col_charoff[col] + if self.col_valign and cell.valign==None: + cell.valign = self.col_valign[col] + # apply column style if specified: + if self.col_styles and cell.style==None: + cell.style = self.col_styles[col] + result += str(cell) + result += ' \n' + return result + +#------------------------------------------------------------------------------- + +class Table (object): + """ + a Table object is used to create a HTML table. (TABLE tag) + + Attributes: + - rows: list, tuple or any iterable, containing one iterable or TableRow + object for each row + - header_row: list, tuple or any iterable, containing the header row (optional) + - border: str or int, border width + - style: str, table style in CSS syntax (thin black borders by default) + - width: str, width of the table on the page + - attribs: dict, additional attributes for the TABLE tag + - col_width: list or tuple defining width for each column + - col_align: list or tuple defining horizontal alignment for each column + - col_char: list or tuple defining alignment character for each column + - col_charoff: list or tuple defining charoff attribute for each column + - col_valign: list or tuple defining vertical alignment for each column + - col_styles: list or tuple of HTML styles for each column + + Reference: http://www.w3.org/TR/html4/struct/tables.html#h-11.2.1 + """ + + def __init__(self, rows=None, border='1', style=None, width=None, + cellspacing=None, cellpadding=4, attribs=None, header_row=None, + col_width=None, col_align=None, col_valign=None, + col_char=None, col_charoff=None, col_styles=None): + """TableCell constructor""" + self.border = border + self.style = style + # style for thin borders by default + if style == None: self.style = TABLE_STYLE_THINBORDER + self.width = width + self.cellspacing = cellspacing + self.cellpadding = cellpadding + self.header_row = header_row + self.rows = rows + if not rows: self.rows = [] + self.attribs = attribs + if not attribs: self.attribs = {} + self.col_width = col_width + self.col_align = col_align + self.col_char = col_char + self.col_charoff = col_charoff + self.col_valign = col_valign + self.col_styles = col_styles + + def __str__(self): + """return the HTML code for the table as a string""" + attribs_str = "" + if self.border: self.attribs['border'] = self.border + if self.style: self.attribs['style'] = self.style + if self.width: self.attribs['width'] = self.width + if self.cellspacing: self.attribs['cellspacing'] = self.cellspacing + if self.cellpadding: self.attribs['cellpadding'] = self.cellpadding + for attr in self.attribs: + attribs_str += ' %s="%s"' % (attr, self.attribs[attr]) + result = '\n' % attribs_str + # insert column tags and attributes if specified: + if self.col_width: + for width in self.col_width: + result += ' \n' % width + # The following code would also generate column attributes for style + # and alignement according to HTML4 specs, + # BUT it is not supported completely (only width) on Mozilla Firefox: + # see https://bugzilla.mozilla.org/show_bug.cgi?id=915 +## n_cols = max(len(self.col_styles), len(self.col_width), +## len(self.col_align), len(self.col_valign)) +## for i in range(n_cols): +## col = '' +## try: +## if self.col_styles[i]: +## col += ' style="%s"' % self.col_styles[i] +## except: pass +## try: +## if self.col_width[i]: +## col += ' width="%s"' % self.col_width[i] +## except: pass +## try: +## if self.col_align[i]: +## col += ' align="%s"' % self.col_align[i] +## except: pass +## try: +## if self.col_valign[i]: +## col += ' valign="%s"' % self.col_valign[i] +## except: pass +## result += '\n' % col + # First insert a header row if specified: + if self.header_row: + if not isinstance(self.header_row, TableRow): + result += str(TableRow(self.header_row, header=True)) + else: + result += str(self.header_row) + # Then all data rows: + for row in self.rows: + if not isinstance(row, TableRow): + row = TableRow(row) + # apply column alignments and styles to each row if specified: + # (Mozilla bug workaround) + if self.col_align and not row.col_align: + row.col_align = self.col_align + if self.col_char and not row.col_char: + row.col_char = self.col_char + if self.col_charoff and not row.col_charoff: + row.col_charoff = self.col_charoff + if self.col_valign and not row.col_valign: + row.col_valign = self.col_valign + if self.col_styles and not row.col_styles: + row.col_styles = self.col_styles + result += str(row) + result += '' + return result + + +#------------------------------------------------------------------------------- + +class List (object): + """ + a List object is used to create an ordered or unordered list in HTML. + (UL/OL tag) + + Attributes: + - lines: list, tuple or any iterable, containing one string for each line + - ordered: bool, choice between an ordered (OL) or unordered list (UL) + - attribs: dict, additional attributes for the OL/UL tag + + Reference: http://www.w3.org/TR/html4/struct/lists.html + """ + + def __init__(self, lines=None, ordered=False, start=None, attribs=None): + """List constructor""" + if lines: + self.lines = lines + else: + self.lines = [] + self.ordered = ordered + self.start = start + if attribs: + self.attribs = attribs + else: + self.attribs = {} + + def __str__(self): + """return the HTML code for the list as a string""" + attribs_str = "" + if self.start: self.attribs['start'] = self.start + for attr in self.attribs: + attribs_str += ' %s="%s"' % (attr, self.attribs[attr]) + if self.ordered: tag = 'OL' + else: tag = 'UL' + result = '<%s%s>\n' % (tag, attribs_str) + for line in self.lines: + result += '
  • %s\n' % str(line) + result += '\n' % tag + return result + + +##class Link (object): +## """ +## a Link object is used to create link in HTML. ( tag) +## +## Attributes: +## - text: str, text of the link +## - url: str, URL of the link +## - attribs: dict, additional attributes for the A tag +## +## Reference: http://www.w3.org/TR/html4 +## """ +## +## def __init__(self, text, url=None, attribs=None): +## """Link constructor""" +## self.text = text +## self.url = url +## if attribs: +## self.attribs = attribs +## else: +## self.attribs = {} +## +## def __str__(self): +## """return the HTML code for the link as a string""" +## attribs_str = "" +## if self.url: self.attribs['href'] = self.url +## for attr in self.attribs: +## attribs_str += ' %s="%s"' % (attr, self.attribs[attr]) +## return '%s' % (attribs_str, text) + + +#=== FUNCTIONS ================================================================ + +# much simpler definition of a link as a function: +def Link(text, url): + return '%s' % (url, text) + +def link(text, url): + return '%s' % (url, text) + +def table(*args, **kwargs): + 'return HTML code for a table as a string. See Table class for parameters.' + return str(Table(*args, **kwargs)) + +def list(*args, **kwargs): + 'return HTML code for a list as a string. See List class for parameters.' + return str(List(*args, **kwargs)) + + +#=== MAIN ===================================================================== + +# Show sample usage when this file is launched as a script. + +if __name__ == '__main__': + + # open an HTML file to show output in a browser + f = open('test.html', 'w') + + t = Table() + t.rows.append(TableRow(['A', 'B', 'C'], header=True)) + t.rows.append(TableRow(['D', 'E', 'F'])) + t.rows.append(('i', 'j', 'k')) + f.write(str(t) + '

    \n') + print str(t) + print '-'*79 + + t2 = Table([ + ('1', '2'), + ['3', '4'] + ], width='100%', header_row=('col1', 'col2'), + col_width=('', '75%')) + f.write(str(t2) + '

    \n') + print t2 + print '-'*79 + + t2.rows.append(['5', '6']) + t2.rows[1][1] = TableCell('new', bgcolor='red') + t2.rows.append(TableRow(['7', '8'], attribs={'align': 'center'})) + f.write(str(t2) + '

    \n') + print t2 + print '-'*79 + + # sample table with column attributes and styles: + table_data = [ + ['Smith', 'John', 30, 4.5], + ['Carpenter', 'Jack', 47, 7], + ['Johnson', 'Paul', 62, 10.55], + ] + htmlcode = HTML.table(table_data, + header_row = ['Last name', 'First name', 'Age', 'Score'], + col_width=['', '20%', '10%', '10%'], + col_align=['left', 'center', 'right', 'char'], + col_styles=['font-size: large', '', 'font-size: small', 'background-color:yellow']) + f.write(htmlcode + '

    \n') + print htmlcode + print '-'*79 + + def gen_table_squares(n): + """ + Generator to create table rows for integers from 1 to n + """ +## # First, header row: +## yield TableRow(('x', 'square(x)'), header=True, bgcolor='blue') +## # Then all rows: + for x in range(1, n+1): + yield (x, x*x) + + t = Table(rows=gen_table_squares(10), header_row=('x', 'square(x)')) + f.write(str(t) + '

    \n') + + print '-'*79 + l = List(['aaa', 'bbb', 'ccc']) + f.write(str(l) + '

    \n') + l.ordered = True + f.write(str(l) + '

    \n') + l.start=10 + f.write(str(l) + '

    \n') + + f.close() diff --git a/HTML/HTML.py b/HTML/HTML.py new file mode 100644 index 0000000..b03c0a2 --- /dev/null +++ b/HTML/HTML.py @@ -0,0 +1,493 @@ +#!/usr/bin/python +# -*- coding: iso-8859-1 -*- +""" +HTML.py - v0.04 2009-07-28 Philippe Lagadec + +This module provides a few classes to easily generate HTML code such as tables +and lists. + +Project website: http://www.decalage.info/python/html + +License: CeCILL (open-source GPL compatible), see source code for details. + http://www.cecill.info +""" + +__version__ = '0.04' +__date__ = '2009-07-28' +__author__ = 'Philippe Lagadec' + +#--- LICENSE ------------------------------------------------------------------ + +# Copyright Philippe Lagadec - see http://www.decalage.info/contact for contact info +# +# This module provides a few classes to easily generate HTML tables and lists. +# +# This software is governed by the CeCILL license under French law and +# abiding by the rules of distribution of free software. You can use, +# modify and/or redistribute the software under the terms of the CeCILL +# license as circulated by CEA, CNRS and INRIA at the following URL +# "http://www.cecill.info". +# +# A copy of the CeCILL license is also provided in these attached files: +# Licence_CeCILL_V2-en.html and Licence_CeCILL_V2-fr.html +# +# As a counterpart to the access to the source code and rights to copy, +# modify and redistribute granted by the license, users are provided only +# with a limited warranty and the software's author, the holder of the +# economic rights, and the successive licensors have only limited +# liability. +# +# In this respect, the user's attention is drawn to the risks associated +# with loading, using, modifying and/or developing or reproducing the +# software by the user in light of its specific status of free software, +# that may mean that it is complicated to manipulate, and that also +# therefore means that it is reserved for developers and experienced +# professionals having in-depth computer knowledge. Users are therefore +# encouraged to load and test the software's suitability as regards their +# requirements in conditions enabling the security of their systems and/or +# data to be ensured and, more generally, to use and operate it in the +# same conditions as regards security. +# +# The fact that you are presently reading this means that you have had +# knowledge of the CeCILL license and that you accept its terms. + + +#--- CHANGES ------------------------------------------------------------------ + +# 2008-10-06 v0.01 PL: - First version +# 2008-10-13 v0.02 PL: - added cellspacing and cellpadding to table +# - added functions to ease one-step creation of tables +# and lists +# 2009-07-21 v0.03 PL: - added column attributes and styles (first attempt) +# (thanks to an idea submitted by Michal Cernoevic) +# 2009-07-28 v0.04 PL: - improved column styles, workaround for Mozilla + + +#------------------------------------------------------------------------------- +#TODO: +# - method to return a generator (yield each row) instead of a single string +# - unicode support (input and output) +# - escape text in cells (optional) +# - constants for standard colors +# - use lxml to generate well-formed HTML ? +# - add classes/functions to generate a HTML page, paragraphs, headings, etc... + + +#--- THANKS -------------------------------------------------------------------- + +# - Michal Cernoevic, for the idea of column styles. + +#--- REFERENCES ---------------------------------------------------------------- + +# HTML 4.01 specs: http://www.w3.org/TR/html4/struct/tables.html + +# Colors: http://www.w3.org/TR/html4/types.html#type-color + +# Columns alignement and style, one of the oldest and trickiest bugs in Mozilla: +# https://bugzilla.mozilla.org/show_bug.cgi?id=915 + + +#--- CONSTANTS ----------------------------------------------------------------- + +# Table style to get thin black lines in Mozilla/Firefox instead of 3D borders +TABLE_STYLE_THINBORDER = "border: 1px solid #000000; border-collapse: collapse;" +#TABLE_STYLE_THINBORDER = "border: 1px solid #000000;" + + +#=== CLASSES =================================================================== + +class TableCell (object): + """ + a TableCell object is used to create a cell in a HTML table. (TD or TH) + + Attributes: + - text: text in the cell (may contain HTML tags). May be any object which + can be converted to a string using str(). + - header: bool, false for a normal data cell (TD), true for a header cell (TH) + - bgcolor: str, background color + - width: str, width + - align: str, horizontal alignement (left, center, right, justify or char) + - char: str, alignment character, decimal point if not specified + - charoff: str, see HTML specs + - valign: str, vertical alignment (top|middle|bottom|baseline) + - style: str, CSS style + - attribs: dict, additional attributes for the TD/TH tag + + Reference: http://www.w3.org/TR/html4/struct/tables.html#h-11.2.6 + """ + + def __init__(self, text="", bgcolor=None, header=False, width=None, + align=None, char=None, charoff=None, valign=None, style=None, + attribs=None): + """TableCell constructor""" + self.text = text + self.bgcolor = bgcolor + self.header = header + self.width = width + self.align = align + self.char = char + self.charoff = charoff + self.valign = valign + self.style = style + self.attribs = attribs + if attribs==None: + self.attribs = {} + + def __str__(self): + """return the HTML code for the table cell as a string""" + attribs_str = "" + if self.bgcolor: self.attribs['bgcolor'] = self.bgcolor + if self.width: self.attribs['width'] = self.width + if self.align: self.attribs['align'] = self.align + if self.char: self.attribs['char'] = self.char + if self.charoff: self.attribs['charoff'] = self.charoff + if self.valign: self.attribs['valign'] = self.valign + if self.style: self.attribs['style'] = self.style + for attr in self.attribs: + attribs_str += ' %s="%s"' % (attr, self.attribs[attr]) + if self.text: + text = str(self.text) + else: + # An empty cell should at least contain a non-breaking space + text = ' ' + if self.header: + return ' %s\n' % (attribs_str, text) + else: + return ' %s\n' % (attribs_str, text) + +#------------------------------------------------------------------------------- + +class TableRow (object): + """ + a TableRow object is used to create a row in a HTML table. (TR tag) + + Attributes: + - cells: list, tuple or any iterable, containing one string or TableCell + object for each cell + - header: bool, true for a header row (TH), false for a normal data row (TD) + - bgcolor: str, background color + - col_align, col_valign, col_char, col_charoff, col_styles: see Table class + - attribs: dict, additional attributes for the TR tag + + Reference: http://www.w3.org/TR/html4/struct/tables.html#h-11.2.5 + """ + + def __init__(self, cells=None, bgcolor=None, header=False, attribs=None, + col_align=None, col_valign=None, col_char=None, + col_charoff=None, col_styles=None): + """TableCell constructor""" + self.bgcolor = bgcolor + self.cells = cells + self.header = header + self.col_align = col_align + self.col_valign = col_valign + self.col_char = col_char + self.col_charoff = col_charoff + self.col_styles = col_styles + self.attribs = attribs + if attribs==None: + self.attribs = {} + + def __str__(self): + """return the HTML code for the table row as a string""" + attribs_str = "" + if self.bgcolor: self.attribs['bgcolor'] = self.bgcolor + for attr in self.attribs: + attribs_str += ' %s="%s"' % (attr, self.attribs[attr]) + result = ' \n' % attribs_str + for cell in self.cells: + col = self.cells.index(cell) # cell column index + if not isinstance(cell, TableCell): + cell = TableCell(cell, header=self.header) + # apply column alignment if specified: + if self.col_align and cell.align==None: + cell.align = self.col_align[col] + if self.col_char and cell.char==None: + cell.char = self.col_char[col] + if self.col_charoff and cell.charoff==None: + cell.charoff = self.col_charoff[col] + if self.col_valign and cell.valign==None: + cell.valign = self.col_valign[col] + # apply column style if specified: + if self.col_styles and cell.style==None: + cell.style = self.col_styles[col] + result += str(cell) + result += ' \n' + return result + +#------------------------------------------------------------------------------- + +class Table (object): + """ + a Table object is used to create a HTML table. (TABLE tag) + + Attributes: + - rows: list, tuple or any iterable, containing one iterable or TableRow + object for each row + - header_row: list, tuple or any iterable, containing the header row (optional) + - border: str or int, border width + - style: str, table style in CSS syntax (thin black borders by default) + - width: str, width of the table on the page + - attribs: dict, additional attributes for the TABLE tag + - col_width: list or tuple defining width for each column + - col_align: list or tuple defining horizontal alignment for each column + - col_char: list or tuple defining alignment character for each column + - col_charoff: list or tuple defining charoff attribute for each column + - col_valign: list or tuple defining vertical alignment for each column + - col_styles: list or tuple of HTML styles for each column + + Reference: http://www.w3.org/TR/html4/struct/tables.html#h-11.2.1 + """ + + def __init__(self, rows=None, border='1', style=None, width=None, + cellspacing=None, cellpadding=4, attribs=None, header_row=None, + col_width=None, col_align=None, col_valign=None, + col_char=None, col_charoff=None, col_styles=None): + """TableCell constructor""" + self.border = border + self.style = style + # style for thin borders by default + if style == None: self.style = TABLE_STYLE_THINBORDER + self.width = width + self.cellspacing = cellspacing + self.cellpadding = cellpadding + self.header_row = header_row + self.rows = rows + if not rows: self.rows = [] + self.attribs = attribs + if not attribs: self.attribs = {} + self.col_width = col_width + self.col_align = col_align + self.col_char = col_char + self.col_charoff = col_charoff + self.col_valign = col_valign + self.col_styles = col_styles + + def __str__(self): + """return the HTML code for the table as a string""" + attribs_str = "" + if self.border: self.attribs['border'] = self.border + if self.style: self.attribs['style'] = self.style + if self.width: self.attribs['width'] = self.width + if self.cellspacing: self.attribs['cellspacing'] = self.cellspacing + if self.cellpadding: self.attribs['cellpadding'] = self.cellpadding + for attr in self.attribs: + attribs_str += ' %s="%s"' % (attr, self.attribs[attr]) + result = '\n' % attribs_str + # insert column tags and attributes if specified: + if self.col_width: + for width in self.col_width: + result += ' \n' % width + # The following code would also generate column attributes for style + # and alignement according to HTML4 specs, + # BUT it is not supported completely (only width) on Mozilla Firefox: + # see https://bugzilla.mozilla.org/show_bug.cgi?id=915 +## n_cols = max(len(self.col_styles), len(self.col_width), +## len(self.col_align), len(self.col_valign)) +## for i in range(n_cols): +## col = '' +## try: +## if self.col_styles[i]: +## col += ' style="%s"' % self.col_styles[i] +## except: pass +## try: +## if self.col_width[i]: +## col += ' width="%s"' % self.col_width[i] +## except: pass +## try: +## if self.col_align[i]: +## col += ' align="%s"' % self.col_align[i] +## except: pass +## try: +## if self.col_valign[i]: +## col += ' valign="%s"' % self.col_valign[i] +## except: pass +## result += '\n' % col + # First insert a header row if specified: + if self.header_row: + if not isinstance(self.header_row, TableRow): + result += str(TableRow(self.header_row, header=True)) + else: + result += str(self.header_row) + # Then all data rows: + for row in self.rows: + if not isinstance(row, TableRow): + row = TableRow(row) + # apply column alignments and styles to each row if specified: + # (Mozilla bug workaround) + if self.col_align and not row.col_align: + row.col_align = self.col_align + if self.col_char and not row.col_char: + row.col_char = self.col_char + if self.col_charoff and not row.col_charoff: + row.col_charoff = self.col_charoff + if self.col_valign and not row.col_valign: + row.col_valign = self.col_valign + if self.col_styles and not row.col_styles: + row.col_styles = self.col_styles + result += str(row) + result += '' + return result + + +#------------------------------------------------------------------------------- + +class List (object): + """ + a List object is used to create an ordered or unordered list in HTML. + (UL/OL tag) + + Attributes: + - lines: list, tuple or any iterable, containing one string for each line + - ordered: bool, choice between an ordered (OL) or unordered list (UL) + - attribs: dict, additional attributes for the OL/UL tag + + Reference: http://www.w3.org/TR/html4/struct/lists.html + """ + + def __init__(self, lines=None, ordered=False, start=None, attribs=None): + """List constructor""" + if lines: + self.lines = lines + else: + self.lines = [] + self.ordered = ordered + self.start = start + if attribs: + self.attribs = attribs + else: + self.attribs = {} + + def __str__(self): + """return the HTML code for the list as a string""" + attribs_str = "" + if self.start: self.attribs['start'] = self.start + for attr in self.attribs: + attribs_str += ' %s="%s"' % (attr, self.attribs[attr]) + if self.ordered: tag = 'OL' + else: tag = 'UL' + result = '<%s%s>\n' % (tag, attribs_str) + for line in self.lines: + result += '

  • %s\n' % str(line) + result += '\n' % tag + return result + + +##class Link (object): +## """ +## a Link object is used to create link in HTML. ( tag) +## +## Attributes: +## - text: str, text of the link +## - url: str, URL of the link +## - attribs: dict, additional attributes for the A tag +## +## Reference: http://www.w3.org/TR/html4 +## """ +## +## def __init__(self, text, url=None, attribs=None): +## """Link constructor""" +## self.text = text +## self.url = url +## if attribs: +## self.attribs = attribs +## else: +## self.attribs = {} +## +## def __str__(self): +## """return the HTML code for the link as a string""" +## attribs_str = "" +## if self.url: self.attribs['href'] = self.url +## for attr in self.attribs: +## attribs_str += ' %s="%s"' % (attr, self.attribs[attr]) +## return '%s' % (attribs_str, text) + + +#=== FUNCTIONS ================================================================ + +# much simpler definition of a link as a function: +def Link(text, url): + return '%s' % (url, text) + +def link(text, url): + return '%s' % (url, text) + +def table(*args, **kwargs): + 'return HTML code for a table as a string. See Table class for parameters.' + return str(Table(*args, **kwargs)) + +def list(*args, **kwargs): + 'return HTML code for a list as a string. See List class for parameters.' + return str(List(*args, **kwargs)) + + +#=== MAIN ===================================================================== + +# Show sample usage when this file is launched as a script. + +if __name__ == '__main__': + + # open an HTML file to show output in a browser + f = open('test.html', 'w') + + t = Table() + t.rows.append(TableRow(['A', 'B', 'C'], header=True)) + t.rows.append(TableRow(['D', 'E', 'F'])) + t.rows.append(('i', 'j', 'k')) + f.write(str(t) + '

    \n') + print str(t) + print '-'*79 + + t2 = Table([ + ('1', '2'), + ['3', '4'] + ], width='100%', header_row=('col1', 'col2'), + col_width=('', '75%')) + f.write(str(t2) + '

    \n') + print t2 + print '-'*79 + + t2.rows.append(['5', '6']) + t2.rows[1][1] = TableCell('new', bgcolor='red') + t2.rows.append(TableRow(['7', '8'], attribs={'align': 'center'})) + f.write(str(t2) + '

    \n') + print t2 + print '-'*79 + + # sample table with column attributes and styles: + table_data = [ + ['Smith', 'John', 30, 4.5], + ['Carpenter', 'Jack', 47, 7], + ['Johnson', 'Paul', 62, 10.55], + ] + htmlcode = HTML.table(table_data, + header_row = ['Last name', 'First name', 'Age', 'Score'], + col_width=['', '20%', '10%', '10%'], + col_align=['left', 'center', 'right', 'char'], + col_styles=['font-size: large', '', 'font-size: small', 'background-color:yellow']) + f.write(htmlcode + '

    \n') + print htmlcode + print '-'*79 + + def gen_table_squares(n): + """ + Generator to create table rows for integers from 1 to n + """ +## # First, header row: +## yield TableRow(('x', 'square(x)'), header=True, bgcolor='blue') +## # Then all rows: + for x in range(1, n+1): + yield (x, x*x) + + t = Table(rows=gen_table_squares(10), header_row=('x', 'square(x)')) + f.write(str(t) + '

    \n') + + print '-'*79 + l = List(['aaa', 'bbb', 'ccc']) + f.write(str(l) + '

    \n') + l.ordered = True + f.write(str(l) + '

    \n') + l.start=10 + f.write(str(l) + '

    \n') + + f.close() diff --git a/HTML/HTML.py.html b/HTML/HTML.py.html new file mode 100644 index 0000000..4a1a80c --- /dev/null +++ b/HTML/HTML.py.html @@ -0,0 +1,206 @@ + + +Python: module HTML + + + + +
     
    + 
    HTML (version 0.04, 2009-07-28)
    index
    html.py
    +

    HTML.py - v0.04 2009-07-28 Philippe Lagadec

    +This module provides a few classes to easily generate HTML code such as tables
    +and lists.

    +Project website: http://www.decalage.info/python/html

    +License: CeCILL (open-source GPL compatible), see source code for details.
    +         http://www.cecill.info

    +

    + + + + + +
     
    +Classes
           
    +
    __builtin__.object +
    +
    +
    List +
    Table +
    TableCell +
    TableRow +
    +
    +
    +

    + + + + + + + +
     
    +class List(__builtin__.object)
       List object is used to create an ordered or unordered list in HTML.
    +(UL/OL tag)

    +Attributes:
    +- lines: list, tuple or any iterable, containing one string for each line
    +- ordered: bool, choice between an ordered (OL) or unordered list (UL)
    +- attribs: dict, additional attributes for the OL/UL tag

    +Reference: http://www.w3.org/TR/html4/struct/lists.html
     
     Methods defined here:
    +
    __init__(self, lines=None, ordered=False, start=None, attribs=None)
    List constructor
    + +
    __str__(self)
    return the HTML code for the list as a string
    + +
    +Data descriptors defined here:
    +
    __dict__
    +
    dictionary for instance variables (if defined)
    +
    +
    __weakref__
    +
    list of weak references to the object (if defined)
    +
    +

    + + + + + + + +
     
    +class Table(__builtin__.object)
       Table object is used to create a HTML table. (TABLE tag)

    +Attributes:
    +- rows: list, tuple or any iterable, containing one iterable or TableRow
    +        object for each row
    +- header_row: list, tuple or any iterable, containing the header row (optional)
    +- border: str or int, border width
    +- style: str, table style in CSS syntax (thin black borders by default)
    +- width: str, width of the table on the page
    +- attribs: dict, additional attributes for the TABLE tag
    +- col_width: list or tuple defining width for each column
    +- col_align: list or tuple defining horizontal alignment for each column
    +- col_char: list or tuple defining alignment character for each column
    +- col_charoff: list or tuple defining charoff attribute for each column
    +- col_valign: list or tuple defining vertical alignment for each column
    +- col_styles: list or tuple of HTML styles for each column

    +Reference: http://www.w3.org/TR/html4/struct/tables.html#h-11.2.1
     
     Methods defined here:
    +
    __init__(self, rows=None, border='1', style=None, width=None, cellspacing=None, cellpadding=4, attribs=None, header_row=None, col_width=None, col_align=None, col_valign=None, col_char=None, col_charoff=None, col_styles=None)
    TableCell constructor
    + +
    __str__(self)
    return the HTML code for the table as a string
    + +
    +Data descriptors defined here:
    +
    __dict__
    +
    dictionary for instance variables (if defined)
    +
    +
    __weakref__
    +
    list of weak references to the object (if defined)
    +
    +

    + + + + + + + +
     
    +class TableCell(__builtin__.object)
       TableCell object is used to create a cell in a HTML table. (TD or TH)

    +Attributes:
    +- text: text in the cell (may contain HTML tags). May be any object which
    +        can be converted to a string using str().
    +- header: bool, false for a normal data cell (TD), true for a header cell (TH)
    +- bgcolor: str, background color
    +- width: str, width
    +- align: str, horizontal alignement (left, center, right, justify or char)
    +- char: str, alignment character, decimal point if not specified
    +- charoff: str, see HTML specs
    +- valign: str, vertical alignment (top|middle|bottom|baseline)
    +- style: str, CSS style
    +- attribs: dict, additional attributes for the TD/TH tag

    +Reference: http://www.w3.org/TR/html4/struct/tables.html#h-11.2.6
     
     Methods defined here:
    +
    __init__(self, text='', bgcolor=None, header=False, width=None, align=None, char=None, charoff=None, valign=None, style=None, attribs=None)
    TableCell constructor
    + +
    __str__(self)
    return the HTML code for the table cell as a string
    + +
    +Data descriptors defined here:
    +
    __dict__
    +
    dictionary for instance variables (if defined)
    +
    +
    __weakref__
    +
    list of weak references to the object (if defined)
    +
    +

    + + + + + + + +
     
    +class TableRow(__builtin__.object)
       TableRow object is used to create a row in a HTML table. (TR tag)

    +Attributes:
    +- cells: list, tuple or any iterable, containing one string or TableCell
    +         object for each cell
    +- header: bool, true for a header row (TH), false for a normal data row (TD)
    +- bgcolor: str, background color
    +- col_align, col_valign, col_char, col_charoff, col_styles: see Table class
    +- attribs: dict, additional attributes for the TR tag

    +Reference: http://www.w3.org/TR/html4/struct/tables.html#h-11.2.5
     
     Methods defined here:
    +
    __init__(self, cells=None, bgcolor=None, header=False, attribs=None, col_align=None, col_valign=None, col_char=None, col_charoff=None, col_styles=None)
    TableCell constructor
    + +
    __str__(self)
    return the HTML code for the table row as a string
    + +
    +Data descriptors defined here:
    +
    __dict__
    +
    dictionary for instance variables (if defined)
    +
    +
    __weakref__
    +
    list of weak references to the object (if defined)
    +
    +

    + + + + + +
     
    +Functions
           
    Link(text, url)
    # much simpler definition of a link as a function:
    +
    link(text, url)
    +
    list(*args, **kwargs)
    return HTML code for a list as a string. See List class for parameters.
    +
    table(*args, **kwargs)
    return HTML code for a table as a string. See Table class for parameters.
    +

    + + + + + +
     
    +Data
           TABLE_STYLE_THINBORDER = 'border: 1px solid #000000; border-collapse: collapse;'
    +__author__ = 'Philippe Lagadec'
    +__date__ = '2009-07-28'
    +__version__ = '0.04'

    + + + + + +
     
    +Author
           Philippe Lagadec
    + \ No newline at end of file diff --git a/HTML/HTML_tutorial.py b/HTML/HTML_tutorial.py new file mode 100644 index 0000000..bf01dde --- /dev/null +++ b/HTML/HTML_tutorial.py @@ -0,0 +1,208 @@ +# HTML.py tutorial - P. Lagadec + +# see also http://www.decalage.info/en/python/html for more details and +# updates. + + +import HTML + +# open an HTML file to show output in a browser +HTMLFILE = 'HTML_tutorial_output.html' +f = open(HTMLFILE, 'w') + + +#=== TABLES =================================================================== + +# 1) a simple HTML table may be built from a list of lists: + +table_data = [ + ['Last name', 'First name', 'Age'], + ['Smith', 'John', 30], + ['Carpenter', 'Jack', 47], + ['Johnson', 'Paul', 62], + ] + +htmlcode = HTML.table(table_data) +print htmlcode +f.write(htmlcode) +f.write('

    ') +print '-'*79 + +#------------------------------------------------------------------------------- + +# 2) a header row may be specified: it will appear in bold in browsers + +table_data = [ + ['Smith', 'John', 30], + ['Carpenter', 'Jack', 47], + ['Johnson', 'Paul', 62], + ] + +htmlcode = HTML.table(table_data, + header_row=['Last name', 'First name', 'Age']) +print htmlcode +f.write(htmlcode) +f.write('

    ') +print '-'*79 + + +#------------------------------------------------------------------------------- + +# 3) you may also create a Table object and add rows one by one: + +t = HTML.Table(header_row=['x', 'square(x)', 'cube(x)']) +for x in range(1,10): + t.rows.append([x, x*x, x*x*x]) +htmlcode = str(t) +print htmlcode +f.write(htmlcode) +f.write('

    ') +print '-'*79 + + +#------------------------------------------------------------------------------- + +# 4) rows may be any iterable (list, tuple, ...) including a generator: +# (this is useful to save memory when generating a large table) + +def gen_rows(i): + 'rows generator' + for x in range(1,i): + yield [x, x*x, x*x*x] + +htmlcode = HTML.table(gen_rows(10), header_row=['x', 'square(x)', 'cube(x)']) +print htmlcode +f.write(htmlcode) +f.write('

    ') +print '-'*79 + + +#------------------------------------------------------------------------------- + +# 5) to choose a specific background color for a cell, use a TableCell +# object: + +HTML_COLORS = ['Black', 'Green', 'Silver', 'Lime', 'Gray', 'Olive', 'White', + 'Maroon', 'Navy', 'Red', 'Blue', 'Purple', 'Teal', 'Fuchsia', 'Aqua'] + +t = HTML.Table(header_row=['Name', 'Color']) +for colorname in HTML_COLORS: + colored_cell = HTML.TableCell(' ', bgcolor=colorname) + t.rows.append([colorname, colored_cell]) +htmlcode = str(t) +print htmlcode +f.write(htmlcode) +f.write('

    ') +print '-'*79 + + +#------------------------------------------------------------------------------- + +# 6) A simple way to generate a test report: + +# dictionary of test results, indexed by test id: +test_results = { + 'test 1': 'success', + 'test 2': 'failure', + 'test 3': 'success', + 'test 4': 'error', + } + +# dict of colors for each result: +result_colors = { + 'success': 'lime', + 'failure': 'red', + 'error': 'yellow', + } + +t = HTML.Table(header_row=['Test', 'Result']) +for test_id in sorted(test_results): + # create the colored cell: + color = result_colors[test_results[test_id]] + colored_result = HTML.TableCell(test_results[test_id], bgcolor=color) + # append the row with two cells: + t.rows.append([test_id, colored_result]) +htmlcode = str(t) +print htmlcode +f.write(htmlcode) +f.write('

    ') +print '-'*79 + +#------------------------------------------------------------------------------- + +# 7) sample table with column attributes and styles: +table_data = [ + ['Smith', 'John', 30, 4.5], + ['Carpenter', 'Jack', 47, 7], + ['Johnson', 'Paul', 62, 10.55], + ] +htmlcode = HTML.table(table_data, + header_row = ['Last name', 'First name', 'Age', 'Score'], + col_width=['', '20%', '10%', '10%'], + col_align=['left', 'center', 'right', 'char'], + col_styles=['font-size: large', '', 'font-size: small', 'background-color:yellow']) +f.write(htmlcode + '

    \n') +print htmlcode +print '-'*79 + + + +#=== LISTS =================================================================== + +# 1) a HTML list (with bullets) may be built from a Python list of strings: + +a_list = ['john', 'paul', 'jack'] +htmlcode = HTML.list(a_list) +print htmlcode +f.write(htmlcode) +f.write('

    ') +print '-'*79 + + +# 2) it is easy to change it into a numbered (ordered) list: + +htmlcode = HTML.list(a_list, ordered=True) +print htmlcode +f.write(htmlcode) +f.write('

    ') +print '-'*79 + + +# 3) Lines of a list may also be added one by one, when using the List class: + +html_list = HTML.List() +for i in range(1,10): + html_list.lines.append('square(%d) = %d' % (i, i*i)) +htmlcode = str(html_list) +print htmlcode +f.write(htmlcode) +f.write('

    ') +print '-'*79 + + +# 4) To save memory, a large list may be built from a generator: + +def gen_lines(i): + 'lines generator' + for x in range(1,i): + yield 'square(%d) = %d' % (x, x*x) +htmlcode = HTML.list(gen_lines(10)) +print htmlcode +f.write(htmlcode) +f.write('

    ') +print '-'*79 + + +#=== LINKS =================================================================== + +# How to create a link: + +htmlcode = HTML.link('Decalage website', 'http://www.decalage.info') +print htmlcode +f.write(htmlcode) +f.write('

    ') +print '-'*79 + + +f.close() +print '\nOpen the file %s in a browser to see the result.' % HTMLFILE diff --git a/HTML/Licence_CeCILL_V2-en.html b/HTML/Licence_CeCILL_V2-en.html new file mode 100644 index 0000000..c573ce2 --- /dev/null +++ b/HTML/Licence_CeCILL_V2-en.html @@ -0,0 +1,1002 @@ + + + + +CeCILL FREE SOFTWARE LICENSINGE + AGREEMENT CeCILL + + + + +

    CeCILL FREE SOFTWARE LICENSE + AGREEMENT

    + + + +
    +

    Notice

    +

    +This +Agreement is a Free Software +license agreement that is the result of +discussions between its authors in order to ensure compliance with +the two main principles guiding its drafting:

    + + + +
      +
    • firstly, +compliance with the principles governing the distribution of Free Software: +access to source code, broad rights granted to users, +
    • +
    • +secondly, the election of a governing law, French law, with which it is +conformant, both as regards the law of torts and +intellectual property law, and the protection that it offers to +both authors and holders of the economic rights over software. +
    • +
    + + +

    The + authors of the + +CeCILL1 +license are:

    + +

    Commissariat +à l'Energie Atomique - CEA, a public +scientific, technical and industrial establishment, having its +principal place of business at 31-33 rue de la Fédération, +75752 Paris +cedex 15, France.

    + +

    Centre +National de la Recherche Scientifique - CNRS, +a public scientific and technological establishment, having its +principal place of business at +3 rue Michel-Ange 75794 Paris cedex 16, France.

    + +

    Institut +National de Recherche en Informatique et en Automatique - INRIA, +a public scientific and technological establishment, having its +principal place of business at Domaine de Voluceau, Rocquencourt, BP +105, 78153 Le Chesnay cedex, France.

    + +
    +
    +

    Preamble

    + +

    The +purpose of this Free Software +license +agreement +is to grant users +the right to modify and redistribute the software governed by this +license within the framework of an open source +distribution model. +

    + +

    The +exercising of these rights is conditional upon certain obligations +for users so as to preserve this status + for all subsequent +redistributions.

    + +

    In consideration of access to +the source code and the rights to copy, +modify and redistribute granted by the license, users are provided only +with a limited warranty and the +software's author, the holder of the economic rights, and the +successive licensors only have limited liability. +

    + +

    In +this respect, + +the risks +associated with loading, using, modifying and/or developing or +reproducing the software by the user +are brought to the user's attention, +given its Free Software +status, +which may make it + complicated to use, + +with the result that its use is +reserved for developers and experienced professionals having in-depth +computer knowledge. +Users are therefore encouraged to load and test the Software's suitability as +regards their requirements in conditions enabling the security of their +systems and/or data to be ensured and, more generally, to use and operate it +in the same conditions of security. This Agreement may be freely reproduced +and published, provided it is not altered, and that no +provisions are either added or removed +herefrom.

    + +

    This +Agreement may apply to any or all software for which the holder of +the economic rights decides to submit the use +thereof to its provisions.

    + +
    +
    +

    Article 1 - DEFINITIONS

    + +

    For + the purpose of this Agreement, + when the following expressions + commence with a capital letter, they shall have the following + meaning:

    + +

    Agreement: + + means this license + agreement, and + its possible subsequent versions and annexes. + +

    + +

    Software: + + means the software in its Object Code and/or Source Code form and, + where applicable, its documentation, "as is" + when the Licensee accepts the Agreement. + +

    + +

    Initial Software: + + means the Software in its Source Code and + possibly its Object Code form and, where applicable, its + documentation, "as is" when it is + first distributed + under the terms and conditions of the + Agreement. + +

    + +

    Modified Software: + + means the Software modified by at least one Contribution. + +

    + +

    Source Code: + + means all the Software's instructions and program lines to which + access is required so as to modify the Software. + +

    + +

    Object Code: + + means the binary files originating from the + compilation of the Source Code. + +

    + +

    Holder: + + means the holder(s) of the economic rights over the Initial Software. + +

    + +

    Licensee: + + means + the Software user(s) having accepted the Agreement. + +

    + +

    Contributor: + + means a Licensee having made at least one Contribution. + +

    + +

    Licensor: + + means the Holder, or any other individual or legal + entity, who distributes the Software under + the Agreement. + +

    + +

    Contribution: + + means any or all modifications, + corrections, translations, adaptations and/or new + functions + integrated into the Software by any or all Contributors, + as well as any or all + Internal Modules. + +

    + +

    Module: + + means a set of sources files including their documentation + that enables + supplementary + functions or services in + addition to those offered by the Software. + +

    + +

    External Module: + + means any or all Modules, + + not derived from the Software, + so that this Module + and the Software run in + separate address spaces, with one calling the other when they are + run. + +

    + +

    Internal Module: + + means any or all + Module, connected to the Software so that they both execute + in the same address space. + + +

    + + +

    GNU GPL: + + means the GNU General Public License version 2 or any subsequent + version, as published by the Free Software Foundation Inc. + +

    + + +

    Parties: + + mean both the Licensee and the Licensor. + +

    + +

    These + expressions may be used both in singular and plural form.

    + +
    +
    +

    Article 2 - PURPOSE

    + +

    The + purpose of the Agreement is the grant + by the Licensor to the + Licensee of a non-exclusive, transferable + and worldwide license + for the Software as set forth in Article 5 + hereinafter for the whole term of the + protection + granted by the rights over said Software. +

    + +
    +
    +

    Article 3 - ACCEPTANCE

    + +
    +

    3.1 The + Licensee shall be deemed as having accepted + the terms and conditions of this Agreement + upon the occurrence of the + first of the following events:

    +
      +
    • (i) + loading the Software by any or all means, notably, by downloading + from a remote server, or by loading from a physical medium;
    • +
    • (ii) + the first time the Licensee exercises any of the rights granted + hereunder.
    • +
    +
    + +
    +

    3.2 One + copy of the Agreement, containing a notice relating to the + characteristics + of the Software, to the limited warranty, and to the + fact that its use is + restricted to + experienced users has been provided to the + Licensee prior to its acceptance as set forth in Article + 3.1 + hereinabove, and the Licensee hereby acknowledges that it + has read and understood it.

    +
    + +
    +
    +

    Article 4 - EFFECTIVE DATE AND TERM

    + + +
    +

    +4.1 EFFECTIVE DATE

    + +

    The + Agreement shall become effective on the date when it is accepted by + the Licensee as set forth in Article 3.1.

    +
    + +
    +

    +4.2 TERM

    + +

    The + Agreement shall remain in force + for the entire legal term of + protection of the economic rights over the Software.

    + +
    + +
    +
    +

    + Article 5 - SCOPE OF RIGHTS GRANTED

    + +

    The + Licensor hereby grants to the Licensee, who + accepts, the + following rights over the Software + for any or all use, and for + the term of the Agreement, on the basis of the terms and conditions + set forth hereinafter. +

    +

    + + Besides, if the Licensor owns or comes to + own one or more patents + protecting all or part of the + functions of the Software + or of its components, + the Licensor undertakes not to enforce the rights granted by these + patents against successive Licensees using, exploiting or + modifying the Software. If these patents are + transferred, the Licensor + undertakes to have the transferees subscribe to the + obligations set forth in this + paragraph. +

    + +
    +

    +5.1 RIGHT OF USE

    + +

    The + Licensee is authorized to use the Software, without any limitation as to its fields + of application, with it being hereinafter + specified that this comprises: +

    +
      +
    1. permanent + or temporary reproduction of all or part of the Software by any or + all means and in any or all form. +

    2. +
    3. loading, + displaying, running, or storing the Software on any or all + medium.

    4. +
    5. entitlement + to observe, study or test + its operation + so as to + determine + the ideas and principles behind any or all + constituent elements of said Software. This shall apply when + the + Licensee carries out any or all loading, displaying, running, + transmission or storage operation as regards the Software, + that it is entitled to carry out hereunder.

    6. +
    + +
    + +
    +

    +5.2 ENTITLEMENT TO MAKE CONTRIBUTIONS

    + +

    + The + right to make Contributions includes the right to translate, adapt, + arrange, or make any or all modifications to the Software, + and the + right to reproduce the resulting Software.

    +

    The + Licensee is authorized to make any or all Contributions to + the Software provided that it + includes an explicit notice that it is the + author + of said Contribution and indicates the date of the + creation thereof.

    + +
    + +
    +

    +5.3 RIGHT OF DISTRIBUTION +

    + +

    In + particular, the right of distribution includes the + right to publish, transmit and communicate + the Software to the general public + on any or all medium, and by any or all means, and the right to + market, either in consideration of a fee, or free of charge, + one or more copies of the Software + by any + means.

    +

    The + Licensee is further authorized to + distribute copies of the + modified or unmodified Software to third parties according to the + terms and conditions set forth hereinafter.

    + +
    +

    +5.3.1 DISTRIBUTION OF SOFTWARE + WITHOUT MODIFICATION

    + +

    The + Licensee is authorized to distribute + true copies of the Software + in Source Code or Object Code form, provided that + said distribution + complies with all the provisions of the + Agreement and is accompanied by:

    +
      +
    1. a + copy of the Agreement,

    2. +
    3. a + notice relating to the limitation of both the Licensor's + warranty and liability as set forth in Articles 8 and 9, +

    4. +
    +

    and + that, in the event that only the Object Code + of the Software is redistributed, the Licensee allows + future Licensees unhindered access to the + + full Source Code of the Software + by + indicating how to access it, + it being understood that the additional cost of acquiring the Source + Code shall not exceed the cost of transferring the data. +

    + +
    + +
    +

    +5.3.2 DISTRIBUTION OF MODIFIED SOFTWARE

    + +

    When + the Licensee makes a Contribution to the Software, the terms and + conditions for the distribution + of the Modified Software become subject to all the provisions + of this Agreement. +

    +

    The + Licensee is authorized to distribute + the Modified Software, in + Source Code or Object Code form, provided that said + distribution + complies with all the provisions of the Agreement and is accompanied + by: +

    +
      +
    1. a + copy of the Agreement,

    2. +
    3. +

      a + notice relating to the limitation of both the Licensor's + warranty and liability as set forth in Articles 8 and 9,

      +
    4. +
    +

    and + that, in the event that only the + Object Code of the Modified Software + is redistributed, the Licensee allows future Licensees + unhindered access to the + full Source Code of the Modified Software + by + indicating how to access it, + it being understood that the additional cost of acquiring the Source + Code shall not exceed the cost of transferring the data. +

    + +
    + +
    +

    +5.3.3 DISTRIBUTION OF + EXTERNAL MODULES

    + +

    When + the Licensee has developed + an External Module, the terms and + conditions + of this Agreement do not apply to said + External Module, that may be + distributed under a separate + license + agreement.

    + +
    + +
    +

    +5.3.4 COMPATIBILITY WITH THE GNU GPL +

    + + + + +

    The Licensee can include a code that is subject to the provisions + of one of the versions of the GNU GPL in the Modified or unmodified + Software, and distribute that entire code under the terms of the + same version of the GNU GPL.

    + +

    The Licensee can include the Modified or unmodified Software in a + code that is subject to the provisions of one of the versions of + the GNU GPL, and distribute that entire code under the terms of + the same version of the GNU GPL. +

    + + +
    + +
    + +
    +
    +

    Article 6 - INTELLECTUAL PROPERTY +

    + +
    +

    +6.1 OVER THE INITIAL SOFTWARE

    + +

    The + Holder owns the economic rights over the Initial Software. Any or + all use of the Initial Software is subject to compliance with the + terms and conditions under which the Holder has elected to + distribute its work and no one shall be entitled to modify the terms + and conditions for the distribution of said Initial Software.

    +

    The + Holder undertakes that the Initial + Software + will remain ruled at least by the current license, + for the duration set + forth in article 4.2.

    + +
    + +
    +

    +6.2 OVER THE CONTRIBUTIONS

    + +

    + + + A Licensee who develops a Contribution is the owner + of the intellectual property rights over this Contribution + as defined by + applicable law.

    + +
    + +
    +

    +6.3 OVER THE + EXTERNAL MODULES

    + +

    + A + Licensee + + who develops an External Module + is the owner of the intellectual property rights over + this External Module as defined by applicable law and is + + free to choose the type of agreement that shall govern its + distribution.

    + +
    + +
    +

    +6.4 JOINT PROVISIONS

    + +
    +

    The + Licensee expressly undertakes:

    +
      +
    1. not + to remove, or modify, in any manner, the + intellectual + property notices + attached to the Software;

    2. +
    3. +

      to + reproduce said notices, in an identical manner, in the copies of + the Software modified or not.

      +
    4. +
    +
    + +
    +

    The + Licensee undertakes not to directly or indirectly infringe + the intellectual property rights of the Holder and/or Contributors + on the Software + and to take, where applicable, vis-à-vis its staff, any + and + all measures required to ensure respect of + said intellectual + property rights of the Holder and/or Contributors.

    +
    + +
    + +
    +
    +

    Article 7 - RELATED SERVICES

    + +
    + +

    7.1 Under + no circumstances shall the Agreement oblige the Licensor to provide + technical assistance or maintenance services for the Software.

    + +

    However, + the Licensor is entitled to offer this type of + services. The terms + and conditions of such technical assistance, and/or such + maintenance, shall be set forth in a separate + instrument. Only + the Licensor offering said maintenance and/or technical assistance + services shall incur liability therefor.

    + +
    + +
    + +

    7.2 Similarly, + any Licensor is entitled to offer to its + licensees, under its + sole responsibility, + a warranty, that shall only + be binding upon itself, for the redistribution of the Software + and/or the Modified Software, under terms and conditions + that + it is free to decide. Said warranty, and the financial + terms and conditions of its application, shall be subject + of a separate + instrument executed between the Licensor and the Licensee.

    + +
    + +
    +
    +

    Article 8 - LIABILITY

    + +
    +

    8.1 Subject + to the provisions of Article 8.2, + + the Licensee shall be entitled to claim compensation for + any direct + loss it may have suffered from the Software + as a result of a fault on the part of the relevant + Licensor, subject to + providing evidence thereof. +

    +
    + +
    +

    8.2 The + Licensor's liability is limited to the commitments made under + this Agreement and shall not be incurred as a result of in + particular: (i) loss due the Licensee's total or partial + failure to fulfill its obligations, (ii) direct or consequential + loss that is suffered by the Licensee due to the + + use or performance of the Software, + + + and (iii) + more generally, any + consequential loss. + In particular + the Parties expressly + agree that any or all pecuniary or business loss (i.e. loss of data, + loss of profits, operating loss, loss of customers or orders, + opportunity cost, any disturbance to business activities) or any or + all legal proceedings instituted against the Licensee by a third + party, shall constitute consequential loss and shall not provide + entitlement to any or all compensation from the Licensor. +

    +
    + +
    +
    +

    Article 9 - WARRANTY

    + +
    +

    9.1 The + Licensee acknowledges that the + scientific and technical + state-of-the-art when the Software + was + distributed did not enable all possible uses to be tested and + verified, nor for the presence of + + possible defects to be detected. + In this respect, the Licensee's attention has been drawn to + the risks associated with loading, using, modifying and/or + developing and reproducing the Software + which are reserved for + experienced users.

    +

    The + Licensee shall be responsible for verifying, by any or all means, + the product's suitability for its requirements, its good working order, and for + ensuring that it shall not cause damage + to either persons or + properties. +

    +
    + +
    +

    9.2 The Licensor + hereby represents, in good faith, that it is entitled to grant all + the rights over the Software (including in + particular the rights set + forth in Article 5).

    +
    + +
    +

    9.3 + The + Licensee acknowledges that the Software is supplied "as is" + by the Licensor without any other express or tacit + warranty, + other than that provided for in Article + 9.2 and, in particular, + without any warranty as to its + + commercial value, its secured, safe, + innovative or relevant nature. +

    +

    Specifically, + the Licensor does not warrant that the Software is free from any + error, that it will + operate without interruption, + that it will be + compatible with the Licensee's own equipment and + software configuration, + nor that it will meet the + Licensee's requirements. +

    +
    + +
    +

    9.4 The + Licensor does not either expressly or tacitly warrant that the + Software does not infringe any third party + intellectual + property right + relating to a patent, software or any + other property right. + Therefore, the Licensor disclaims any and all liability towards the + Licensee arising out of any or all proceedings for + infringement that may be + instituted in respect of the use, modification and redistribution of + the Software. Nevertheless, should such proceedings be instituted + against the Licensee, the Licensor shall provide it with technical + and legal assistance for its defense. Such technical and legal + assistance shall be decided on a case-by-case basis + between the + relevant Licensor and the Licensee pursuant to a memorandum of + understanding. The Licensor disclaims any and + all liability as + regards the Licensee's use of the + + name of the Software. No + warranty is given as regards + the existence of prior rights + over the name of the Software or as regards + the existence of a trademark.

    +
    + +
    +
    +

    Article 10 - TERMINATION

    + +
    +

    10.1 + In the event of a breach by the + Licensee of its obligations hereunder, the Licensor may + automatically terminate this Agreement thirty (30) + days after + notice has been sent to the Licensee and has remained ineffective.

    +
    + +
    +

    10.2 A + Licensee whose Agreement is terminated shall no longer be authorized + to use, modify or distribute the Software. However, any + + licenses that it may have granted prior to termination of the + Agreement shall remain valid subject to their having been granted in + compliance with the terms and conditions hereof. +

    +
    + +
    +
    +

    Article 11 - MISCELLANEOUS

    + +
    +

    +11.1 EXCUSABLE EVENTS

    + +

    Neither + Party shall be liable for any or all delay, or failure to perform + the Agreement, that may be attributable to an event of force + majeure, an act of God or an outside cause, such as + + defective functioning or interruptions + of the electricity or + telecommunications networks, network + paralysis following a + virus attack, intervention by + government authorities, + natural disasters, water damage, + earthquakes, fire, explosions, + strikes and labor unrest, war, etc.

    + +
    + +
    +

    11.2 Any Failure by either + Party, on one or more + occasions, to invoke one or more of the + provisions hereof, shall under no + circumstances be interpreted as being a waiver by the interested + Party of its right to invoke said + provision(s) subsequently.

    +
    + +
    +

    11.3 The + Agreement cancels and replaces + any or all previous agreements, whether written or oral, + between the + Parties and having the same purpose, and constitutes the entirety of + the agreement between said Parties concerning said purpose. No + supplement or modification to the terms and conditions hereof shall + be effective as between the Parties + unless it is made in writing and + signed by their duly authorized representatives. +

    +
    + +
    +

    11.4 In + the event that one or more + of the provisions hereof were to conflict with a current or future + applicable act or legislative text, said act or legislative text + shall prevail, and the Parties + shall make the necessary + amendments so as to comply with + said act or legislative + text. All other provisions shall remain + effective. Similarly, + invalidity of a provision of the + Agreement, + for any reason whatsoever, shall not cause the Agreement as a whole + to be invalid. +

    +
    + +
    +

    +11.5 LANGUAGE

    + +

    The + Agreement is drafted in both French and English + and both versions are + deemed authentic. +

    +
    + +
    +
    +

    Article 12 - NEW VERSIONS OF THE AGREEMENT

    + +
    +

    12.1 Any + person + is authorized to duplicate and distribute copies of + this Agreement.

    +
    + +
    +

    12.2 So + as to ensure coherence, the wording of this Agreement is protected + and may only be modified by the authors of the License, + who reserve + the right to periodically publish updates or new versions of the + Agreement, each with a separate number. These subsequent versions + may + address new issues encountered by Free Software. +

    +
    + +
    +

    12.3 Any + + Software distributed under a given version of the Agreement + may only be subsequently distributed under the same version of the + Agreement or a subsequent version, subject to the provisions of + Article 5.3.4. +

    +
    +
    +
    +

    Article 13 - GOVERNING LAW AND JURISDICTION

    + +
    +

    13.1 + The Agreement is governed by French law. The + Parties agree to endeavor to seek an amicable + solution to any disagreements or disputes + that may arise during the performance of + the Agreement. +

    +
    + +
    +

    13.2 Failing an amicable solution + within two (2) months as from their + occurrence, and unless emergency proceedings are necessary, the + disagreements + or disputes shall be referred to the Paris Courts having + jurisdiction, by the more diligent + Party. +

    +
    + +
    + + + +
    Version 2.0 dated 2005-05-21.
    + + diff --git a/HTML/Licence_CeCILL_V2-fr.html b/HTML/Licence_CeCILL_V2-fr.html new file mode 100644 index 0000000..d2324be --- /dev/null +++ b/HTML/Licence_CeCILL_V2-fr.html @@ -0,0 +1,968 @@ + + + + +CONTRAT DE LICENCE DE LOGICIEL LIBRE CeCILL + + + +

    CONTRAT DE LICENCE DE LOGICIEL LIBRE CeCILL

    + + + +
    +

    Avertissement

    + +

    Ce +contrat est une licence de logiciel libre issue d'une +concertation entre ses auteurs afin que le respect de deux grands +principes préside à sa rédaction:

    + + + +
      +
    • +d'une part, le respect des principes de diffusion des logiciels libres: +accès au code source, droits étendus conférés +aux utilisateurs,
    • +
    • +d'autre +part, la désignation d'un droit applicable, le droit français, auquel +elle est conforme, tant au regard +du droit de la responsabilité civile que du droit de la +propriété intellectuelle et de la protection qu'il +offre aux auteurs et titulaires des droits patrimoniaux sur un +logiciel.
    • +
    + + + +

    Les +auteurs de la licence +CeCILL1 + +sont:

    + +

    Commissariat +à l'Energie Atomique - CEA, établissement +public de caractère scientifique technique et industriel, dont +le siège est situé 31-33 rue de la Fédération, +75752 Paris cedex 15.

    +

    Centre +National de la Recherche Scientifique - CNRS, +établissement public à caractère scientifique et +technologique, dont le siège est situé 3 rue +Michel-Ange 75794 Paris cedex 16.

    + +

    Institut +National de Recherche en Informatique et en Automatique - INRIA, +établissement public à caractère scientifique et +technologique, dont le siège est situé Domaine de +Voluceau, Rocquencourt, BP 105, 78153 Le Chesnay cedex.

    + +
    +
    +

    Préambule

    + +

    Ce +contrat est une licence de logiciel libre dont l'objectif est de +conférer aux utilisateurs la liberté de modification et +de redistribution du logiciel régi par cette licence dans le +cadre d'un modèle de diffusion + +en logiciel libre. +

    +

    L'exercice +de ces libertés est assorti de certains devoirs à la +charge des utilisateurs afin de préserver ce statut au cours +des redistributions ultérieures. +

    + +

    L'accessibilité +au code source et les droits de copie, de modification et de +redistribution qui en découlent ont pour contrepartie de +n'offrir aux utilisateurs qu'une garantie limitée +et de ne faire peser sur l'auteur du logiciel, le titulaire des +droits patrimoniaux et les concédants successifs qu'une +responsabilité restreinte. +

    + +

    A +cet égard l'attention de l'utilisateur est attirée +sur les risques associés au chargement, à +l'utilisation, à la modification et/ou au développement +et à la reproduction du logiciel par l'utilisateur étant +donné sa spécificité de logiciel libre, qui peut +le rendre complexe à manipuler et qui le réserve donc à +des développeurs ou des professionnels avertis +possédant des connaissances informatiques approfondies. Les utilisateurs sont +donc invités à charger et tester l'adéquation +du Logiciel à leurs besoins dans des conditions permettant +d'assurer la sécurité de leurs systèmes et/ou de leurs données et, +plus généralement, à l'utiliser et l'exploiter dans les même conditions de +sécurité. Ce contrat peut être reproduit et diffusé librement, sous réserve +de le conserver en l'état, sans ajout ni suppression de +clauses. +

    +

    Ce +contrat est susceptible de s'appliquer à tout logiciel +dont le titulaire des droits patrimoniaux décide de soumettre +l'exploitation aux dispositions qu'il contient.

    + +
    +
    + +

    Article 1 - DEFINITIONS

    + +

    Dans + ce contrat, les termes suivants, lorsqu'ils seront écrits + avec une lettre capitale, auront la signification suivante:

    + +

    Contrat: + + désigne le présent contrat de licence, ses + éventuelles versions postérieures et annexes. + +

    + +

    Logiciel: + + désigne le logiciel sous sa forme de Code Objet et/ou de Code + Source et le cas échéant sa documentation, dans leur + état au moment de l'acceptation du Contrat par le + Licencié. + +

    + +

    Logiciel Initial: + + désigne le Logiciel sous sa forme de + Code Source et éventuellement + de Code Objet et le cas échéant sa + documentation, dans leur état au moment de leur première + diffusion sous les termes du Contrat. + +

    + +

    Logiciel Modifié: + + désigne le Logiciel modifié par au moins une Contribution. +

    + +

    Code Source: + + désigne l'ensemble des + instructions et des lignes de programme du Logiciel et auquel + l'accès est nécessaire en vue de modifier le + Logiciel. + +

    + +

    Code Objet: + + désigne les fichiers binaires issus de la compilation du + Code Source. + +

    + +

    Titulaire: + + désigne le ou les détenteurs des droits + patrimoniaux d'auteur sur le Logiciel Initial + +

    + +

    Licencié: + + désigne le ou les utilisateurs du Logiciel + ayant accepté + le Contrat. + +

    + +

    Contributeur: + + désigne le Licencié auteur d'au moins une Contribution. +

    + +

    Concédant: + + désigne le Titulaire ou toute personne physique ou morale + distribuant le Logiciel sous le Contrat. + +

    + +

    Contribution: + + désigne l'ensemble des modifications, corrections, + traductions, adaptations et/ou nouvelles fonctionnalités + intégrées dans le Logiciel par tout Contributeur, + ainsi que tout + Module + Interne. + +

    + +

    Module: + + désigne un ensemble de fichiers sources y compris leur + documentation qui + permet de réaliser des fonctionnalités ou services + supplémentaires à ceux fournis par le Logiciel. + +

    + +

    Module Externe: + + désigne tout Module, non dérivé du Logiciel, tel que ce + Module et le Logiciel s'exécutent dans + + des espaces d'adressages différents, + l'un appelant l'autre au moment de leur exécution. + +

    + +

    Module Interne: + + désigne tout Module + lié au Logiciel + de telle sorte qu'ils s'exécutent + dans le même espace d'adressage. + +

    + + + +

    GNU GPL: + + désigne la GNU General Public License dans sa version 2 ou toute + version ultérieure, telle que publiée par Free Software Foundation + Inc. + +

    + + + +

    Parties: + désigne collectivement le Licencié et le Concédant. +

    + +

    Ces termes s'entendent au singulier comme au pluriel.

    + +
    +
    + +

    Article 2 - OBJET

    + +

    Le + Contrat a pour objet la concession par le Concédant au + Licencié d'une + licence + non exclusive, + cessible + et mondiale du Logiciel telle + que définie ci-après à + l'article 5 pour toute la durée de protection + des droits portant sur ce Logiciel. +

    + +
    +
    + +

    Article 3 - ACCEPTATION

    + +
    +

    3.1 + L'acceptation + par le Licencié des termes du Contrat est réputée + acquise du fait du premier des faits suivants: +

    +
      +
    • (i) + le chargement du Logiciel par tout moyen notamment par + téléchargement à partir d'un serveur + distant ou par chargement à partir d'un support + physique;
    • +
    • + (ii) + le premier exercice par le Licencié de l'un quelconque + des droits concédés par le Contrat.
    • +
    +
    + +
    +

    3.2 Un + exemplaire du Contrat, contenant notamment un avertissement relatif + aux spécificités du Logiciel, à la restriction + de garantie et à la limitation à un usage par des + utilisateurs expérimentés a été mis à + disposition du Licencié préalablement à son + acceptation telle que définie à l'article + 3.1 ci + dessus et le Licencié reconnaît en avoir pris + connaissance.

    +
    + +
    +
    + +

    Article 4 - ENTREE EN VIGUEUR ET DUREE

    + +
    +

    +4.1 ENTREE EN VIGUEUR

    + +

    Le + Contrat entre en vigueur à la date de son acceptation par le + Licencié telle que définie en 3.1.

    + +
    + +
    +

    +4.2 DUREE

    + +

    Le + Contrat produira ses effets pendant toute la durée légale + de protection des droits patrimoniaux portant sur le Logiciel.

    + +
    + +
    +
    + +

    + Article 5 - ETENDUE DES DROITS CONCEDES

    + +

    Le + Concédant concède au Licencié, qui accepte, les + droits suivants sur le Logiciel pour toutes destinations et pour la + durée du Contrat dans les conditions ci-après + détaillées. +

    +

    + Par ailleurs, + + + si le Concédant détient ou venait à détenir un ou plusieurs + brevets d'invention protégeant tout ou partie des fonctionnalités + du Logiciel ou de ses composants, il s'engage à ne pas + opposer les éventuels droits conférés par ces brevets aux Licenciés + successifs qui utiliseraient, exploiteraient ou modifieraient le + Logiciel. En cas de cession de ces brevets, le + Concédant s'engage à faire reprendre les obligations du présent alinéa + aux cessionnaires. +

    + +
    +

    +5.1 DROIT D'UTILISATION

    + +

    Le + Licencié est autorisé à utiliser le Logiciel, + sans restriction quant aux domaines d'application, étant + ci-après précisé que cela comporte:

    +
      +
    1. la + reproduction permanente ou provisoire du Logiciel en tout ou partie + par tout moyen et sous toute forme. +

    2. +
    3. +

      le + chargement, l'affichage, l'exécution, ou le + stockage du Logiciel sur tout support.

      +
    4. +
    5. +

      la + possibilité d'en observer, d'en étudier, + ou d'en tester le fonctionnement afin de déterminer + les idées et principes qui sont à la base de + n'importe quel élément de ce Logiciel; et + ceci, lorsque le Licencié effectue toute opération de + chargement, d'affichage, d'exécution, de + transmission ou de stockage du Logiciel qu'il est en droit + d'effectuer en vertu du Contrat.

      +
    6. +
    + +
    + +
    +

    +5.2 DROIT D'APPORTER DES CONTRIBUTIONS

    + +

    + Le + droit d'apporter des Contributions comporte le droit de + traduire, d'adapter, d'arranger ou d'apporter + toute autre modification + au Logiciel et le droit de reproduire le + Logiciel en résultant.

    +

    Le + Licencié est autorisé à apporter toute + Contribution au Logiciel sous réserve de mentionner, de façon + explicite, son nom en tant qu'auteur de cette Contribution et + la date de création de celle-ci.

    + +
    + +
    +

    +5.3 DROIT + DE DISTRIBUTION

    + +

    Le + droit de distribution + comporte notamment le droit de diffuser, de + transmettre et de communiquer le Logiciel au public sur tout + support et par tout moyen ainsi que le droit de mettre sur le marché + à titre onéreux ou gratuit, un ou des exemplaires du + Logiciel par tout procédé.

    +

    Le + Licencié est autorisé à + distribuer des copies + du Logiciel, modifié ou non, à des tiers dans les + conditions ci-après détaillées.

    + +
    +

    +5.3.1 DISTRIBUTION + DU LOGICIEL SANS MODIFICATION

    + +

    Le + Licencié est autorisé à + distribuer des copies + conformes du Logiciel, sous forme de Code Source ou de Code Objet, + à condition que cette distribution respecte les + dispositions du Contrat dans leur totalité et soit accompagnée:

    +
      +
    1. d'un + exemplaire du Contrat,

    2. +
    3. d'un + avertissement relatif à la restriction de garantie et de + responsabilité du Concédant telle que prévue + aux articles 8 et + 9,

    4. +
    +

    et + que, dans le cas où seul le Code Objet du Logiciel est + redistribué, le Licencié permette aux futurs + Licenciés + d'accéder facilement au Code Source complet du Logiciel + en indiquant les modalités d'accès, étant + entendu que le coût additionnel d'acquisition du Code + Source ne devra pas excéder le simple coût de transfert + des données.

    + +
    + +
    +

    +5.3.2 DISTRIBUTION + DU LOGICIEL MODIFIE

    + + +

    Lorsque + le Licencié apporte une Contribution au Logiciel, les + conditions de distribution du Logiciel Modifié sont alors + soumises à l'intégralité des dispositions + du Contrat. +

    +

    Le + Licencié est autorisé à distribuer le + Logiciel Modifié, sous forme de Code Source ou de Code Objet, + à condition que cette distribution respecte les + dispositions du Contrat dans leur totalité et soit + accompagnée: +

    +
      +
    1. d'un + exemplaire du Contrat,

    2. +
    3. +

      d'un + avertissement relatif à la restriction de garantie et de + responsabilité du Concédant telle que + prévue aux articles 8 et + 9,

      +
    4. +
    +

    et que, dans le cas où seul le Code Objet du Logiciel + Modifié est redistribué, le Licencié permette aux futurs + Licenciés d'accéder facilement au Code Source complet du Logiciel + Modifié en indiquant les modalités d'accès, étant entendu que le coût + additionnel d'acquisition du Code Source ne devra pas excéder le + simple coût de transfert des données.

    + + + +
    + +
    +

    +5.3.3 DISTRIBUTION DES MODULES + + EXTERNES

    + +

    Lorsque le Licencié a développé un Module + Externe les conditions du Contrat ne s'appliquent pas à ce + Module Externe, qui peut être + distribué sous un contrat de licence différent.

    + +
    + +
    + + +

    +5.3.4 COMPATIBILITE AVEC LA LICENCE + GNU GPL

    + + + + +

    Le Licencié peut inclure un code soumis aux dispositions d'une + des versions de la licence GNU GPL dans le Logiciel modifié ou non et + distribuer l'ensemble sous les conditions de la même version de la + licence GNU GPL. +

    + +

    Le Licencié peut inclure le Logiciel modifié ou non dans un code + soumis aux dispositions d'une des versions de la licence GNU GPL et + distribuer l'ensemble sous les conditions de la même version de la + licence GNU GPL. +

    + + + + + + +
    + +
    + +
    +
    + +

    Article 6 - PROPRIETE INTELLECTUELLE

    + +
    +

    +6.1 SUR LE LOGICIEL INITIAL

    + +

    Le + Titulaire est détenteur des droits patrimoniaux sur le + Logiciel Initial. Toute utilisation du Logiciel Initial est soumise + au respect des conditions dans lesquelles le Titulaire a choisi de + diffuser son oeuvre et nul autre n'a la faculté de + modifier les conditions de diffusion de ce Logiciel Initial. +

    +

    Le + Titulaire s'engage à + ce que le Logiciel Initial + + reste au moins régi par la présente + licence + et ce, pour la durée visée à l'article 4.2.

    + +
    + +
    +

    +6.2 SUR LES CONTRIBUTIONS

    + +

    + + + Le Licencié qui a développé une Contribution est titulaire + sur celle-ci des droits de propriété intellectuelle dans les conditions + définies par la législation applicable. + +

    + +
    + +
    +

    +6.3 SUR LES MODULES + EXTERNES

    + +

    + Le + Licencié + qui a développé un Module Externe est titulaire + sur celui-ci des droits de propriété intellectuelle dans les conditions + définies par la législation applicable + et reste + libre du choix du contrat régissant + sa diffusion.

    + +
    + +
    +

    +6.4 DISPOSITIONS COMMUNES

    + + +
    +

    + Le Licencié s'engage expressément:

    +
      +
    1. +

      à + ne pas supprimer ou modifier de quelque manière que ce soit + les mentions de propriété intellectuelle apposées + sur le Logiciel;

      +
    2. +
    3. +

      à reproduire à l'identique lesdites mentions de + propriété intellectuelle sur les copies du Logiciel modifié ou + non.

      +
    4. +
    +
    + + + + +
    +

    Le + Licencié s'engage à ne pas porter atteinte, + directement ou indirectement, aux droits de propriété + intellectuelle du Titulaire et/ou des Contributeurs + sur le Logiciel et à + prendre, le cas échéant, à l'égard + de son personnel toutes les mesures nécessaires pour assurer + le respect des dits droits de propriété intellectuelle + du Titulaire et/ou des Contributeurs.

    +
    + +
    + +
    +
    + +

    Article 7 - SERVICES ASSOCIES

    + +
    +

    7.1 Le + Contrat n'oblige en aucun cas le Concédant à la + réalisation de prestations d'assistance technique ou de + maintenance du Logiciel.

    +

    Cependant + le Concédant reste libre de proposer ce type de services. Les + termes et conditions d'une telle assistance technique et/ou + d'une telle maintenance seront alors déterminés + dans un acte séparé. Ces actes de maintenance et/ou + assistance technique n'engageront que la seule responsabilité + du Concédant qui les propose.

    +
    + +
    +

    7.2 De + même, tout Concédant est libre de proposer, sous sa + seule responsabilité, à ses licenciés une + garantie, qui n'engagera que lui, lors de la redistribution du + Logiciel et/ou du Logiciel Modifié et ce, dans les conditions + qu'il souhaite. Cette garantie et les modalités + financières de son application feront l'objet d'un + acte séparé entre le Concédant et le Licencié.

    +
    + +
    +
    + +

    + Article 8 - RESPONSABILITE

    + +
    +

    8.1 Sous + réserve des dispositions de + l'article 8.2, + + le Licencié a la faculté, sous réserve de prouver la faute du + Concédant concerné, de solliciter la réparation + du préjudice direct qu'il subirait du fait du + logiciel et dont il apportera la preuve. +

    +
    + +
    +

    8.2 + La + responsabilité du Concédant est limitée aux + engagements pris en application du Contrat et ne saurait être + engagée en raison notamment: (i) des dommages dus à + l'inexécution, totale ou partielle, de ses obligations + par le Licencié, (ii) des dommages directs ou indirects + découlant de l'utilisation ou des performances du + Logiciel subis par le Licencié + + et (iii) + plus généralement d'un quelconque + dommage + indirect. + En particulier, les Parties + conviennent expressément que tout préjudice financier + ou commercial (par exemple perte de données, perte de + bénéfices, perte d'exploitation, perte de + clientèle ou de commandes, manque à gagner, trouble + commercial quelconque) ou toute action dirigée contre le + Licencié par un tiers, constitue un dommage indirect et + n'ouvre pas droit à réparation par le + Concédant. +

    +
    + +
    +
    + +

    + Article 9 - GARANTIE

    + +
    +

    9.1 + Le + Licencié reconnaît que l'état actuel des + connaissances scientifiques et techniques au moment de la mise en + circulation du Logiciel ne permet pas d'en tester et d'en + vérifier toutes les utilisations ni de détecter + l'existence d'éventuels défauts. + L'attention du Licencié a été attirée + sur ce point sur les risques associés au chargement, à + l'utilisation, la modification et/ou au développement + et à la reproduction du Logiciel qui sont réservés + à des utilisateurs avertis.

    +

    Il + relève de la responsabilité du Licencié de + contrôler, par tous moyens, l'adéquation du + produit à ses besoins, son bon fonctionnement et de s'assurer + qu'il ne causera pas de dommages aux personnes et aux biens. +

    +
    + +
    +

    9.2 + Le Concédant déclare de bonne foi être en droit + de concéder l'ensemble des droits attachés au Logiciel + (comprenant notamment les droits visés à l'article + 5). +

    +
    + +
    +

    9.3 Le + Licencié reconnaît que le Logiciel est fourni "en + l'état" par le Concédant sans autre + garantie, expresse ou tacite, que celle prévue à + l'article 9.2 et notamment sans aucune garantie sur sa + valeur commerciale, son caractère sécurisé, innovant + ou pertinent. +

    +

    En + particulier, le Concédant ne garantit pas que le Logiciel est + exempt d'erreur, qu'il fonctionnera sans interruption, + qu'il + sera compatible avec l'équipement du Licencié et + sa configuration logicielle ni qu'il remplira les besoins du + Licencié.

    +
    + +
    +

    9.4 Le + Concédant ne garantit pas, de manière expresse ou + tacite, que le Logiciel ne porte pas atteinte à un quelconque + droit de propriété intellectuelle d'un tiers + portant sur un brevet, un logiciel ou sur tout autre droit de + propriété. Ainsi, le Concédant exclut toute + garantie au profit du Licencié contre les actions en + contrefaçon qui pourraient être diligentées au + titre de l'utilisation, de la modification, et de la + redistribution du Logiciel. Néanmoins, si de telles actions + sont exercées contre le Licencié, le Concédant + lui apportera son aide technique et juridique pour sa défense. + Cette aide technique et juridique est déterminée au + cas par cas entre le Concédant concerné et le + Licencié + dans le cadre d'un protocole d'accord. Le Concédant + dégage toute responsabilité quant à + l'utilisation de la dénomination du Logiciel par le + Licencié. Aucune garantie n'est apportée quant + à + l'existence de droits antérieurs sur le nom du Logiciel + et sur l'existence d'une marque.

    +
    + +
    +
    + +

    Article 10 - RESILIATION

    + +
    +

    10.1 En + cas de manquement par le Licencié aux obligations mises à + sa charge par le Contrat, le Concédant pourra résilier + de plein droit le Contrat trente (30) jours après + notification adressée au Licencié et restée + sans effet.

    +
    + +
    +

    10.2 Le + Licencié dont le Contrat est résilié n'est + plus autorisé à utiliser, modifier ou distribuer le + Logiciel. Cependant, toutes les licences qu'il aura + concédées + antérieurement à la résiliation du Contrat + resteront valides sous réserve qu'elles aient + été + effectuées en conformité avec le Contrat.

    +
    + +
    +
    + +

    Article 11 - DISPOSITIONS DIVERSES

    + +
    +

    +11.1 CAUSE EXTERIEURE

    + +

    Aucune + des Parties ne sera responsable d'un retard ou d'une + défaillance d'exécution du Contrat qui serait dû + à un cas de force majeure, un cas fortuit ou une cause + extérieure, telle que, notamment, le mauvais fonctionnement + ou les interruptions du réseau électrique ou de + télécommunication, la paralysie du réseau liée + à une attaque informatique, l'intervention des + autorités gouvernementales, les catastrophes naturelles, les + dégâts des eaux, les tremblements de terre, le feu, les + explosions, les grèves et les conflits sociaux, l'état + de guerre...

    +
    + +
    +

    11.2 Le + fait, par l'une ou l'autre des Parties, d'omettre + en une ou plusieurs occasions de se prévaloir d'une ou + plusieurs dispositions du Contrat, ne pourra en aucun cas impliquer + renonciation par la Partie intéressée à s'en + prévaloir ultérieurement.

    +
    + +
    +

    11.3 Le + Contrat annule et remplace toute convention antérieure, + écrite ou orale, entre les Parties sur le même objet et + constitue l'accord entier entre les Parties sur cet objet. + Aucune addition ou modification aux termes du Contrat n'aura + d'effet à l'égard des Parties à + moins d'être faite par écrit et signée par + leurs représentants dûment habilités.

    +
    + +
    +

    11.4 Dans + l'hypothèse où une ou plusieurs des dispositions + du Contrat s'avèrerait contraire à une loi ou à + un texte applicable, existants ou futurs, cette loi ou ce texte + prévaudrait, et les Parties feraient les amendements + nécessaires pour se conformer à cette loi ou à + ce texte. Toutes les autres dispositions resteront en vigueur. De + même, la nullité, pour quelque raison que ce soit, + d'une des dispositions du Contrat ne saurait entraîner + la nullité de l'ensemble du Contrat.

    +
    + +
    +

    +11.5 LANGUE

    +

    Le + Contrat est rédigé en langue française et en + langue anglaise, ces deux versions + faisant également foi. +

    +
    + +
    +
    + +

    Article 12 - NOUVELLES VERSIONS DU CONTRAT

    + +
    +

    12.1 Toute personne est autorisée à copier et distribuer des + copies de ce Contrat.

    +
    + +
    +

    12.2 Afin d'en préserver la cohérence, le texte du Contrat + est protégé et ne peut être modifié que + par les auteurs de la licence, lesquels se réservent le droit + de publier périodiquement des mises à jour ou de + nouvelles versions du Contrat, qui possèderont chacune un + numéro distinct. Ces versions ultérieures seront + susceptibles de prendre en compte de nouvelles problématiques + rencontrées par les logiciels libres.

    +
    + +
    +

    12.3 Tout + Logiciel diffusé sous une version donnée du Contrat ne + pourra faire l'objet d'une diffusion ultérieure que sous la + même version du Contrat ou une version postérieure, + sous réserve des dispositions de l'article + 5.3.4.

    +
    + +
    +
    + +

    Article 13 - LOI APPLICABLE ET COMPETENCE TERRITORIALE

    + +
    +

    13.1 + Le Contrat est régi par la loi + française. Les Parties conviennent de tenter de régler + à l'amiable les différends ou litiges qui + viendraient à se produire par suite ou à l'occasion + du Contrat. +

    +
    + +
    +

    13.2 + A défaut d'accord amiable dans un délai de deux + (2) mois à compter de leur survenance et sauf situation + relevant d'une procédure d'urgence, les + différends ou litiges seront portés par la Partie la + plus diligente devant les Tribunaux compétents de + Paris.

    +
    + +
    + + + +
    Version 2.0 du 2005-05-21.
    + + \ No newline at end of file diff --git a/HTML/PKG-INFO b/HTML/PKG-INFO new file mode 100644 index 0000000..79c627c --- /dev/null +++ b/HTML/PKG-INFO @@ -0,0 +1,20 @@ +Metadata-Version: 1.0 +Name: HTML.py +Version: 0.04 +Summary: A Python module to easily generate HTML code (tables, lists, ...). +See http://www.decalage.info/python/html for more information. + +Home-page: http://www.decalage.info/python/html +Author: Philippe Lagadec +Author-email: decalage (a) laposte.net +License: CeCILL (open-source GPL compatible) +Download-URL: http://www.decalage.info/python/html +Description: UNKNOWN +Platform: UNKNOWN +Classifier: Development Status :: 4 - Beta +Classifier: Natural Language :: English +Classifier: Intended Audience :: Developers +Classifier: Topic :: Internet :: WWW/HTTP +Classifier: Operating System :: OS Independent +Classifier: Programming Language :: Python +Classifier: Topic :: Software Development :: Libraries :: Python Modules diff --git a/HTML/README.txt b/HTML/README.txt new file mode 100644 index 0000000..7529225 --- /dev/null +++ b/HTML/README.txt @@ -0,0 +1,29 @@ +HTML.py + +This module provides a few classes to easily generate HTML tables and lists. + +Author: Philippe Lagadec + +Project website: http://www.decalage.info/python/html + +License: CeCILL (open-source GPL compatible), see source code for details. + http://www.cecill.info + +------------------------------------------------------------------------------- + +INSTALLATION: + +- on Windows, double-click on install.bat, or type "setup.py install" in a CMD + window. +- on other systems, type "python setup.py install" in a shell. + +------------------------------------------------------------------------------- + +HOW TO USE THIS MODULE: + +First have a look at HTML_tutorial.py. It provides examples of how to use +HTML.py. +See http://www.decalage.info/python/html for additional information and updates. +For complete reference see HTML.py.html, and also the source code of HTML.py. + + diff --git a/HTML/setup.py b/HTML/setup.py new file mode 100644 index 0000000..0c23afe --- /dev/null +++ b/HTML/setup.py @@ -0,0 +1,41 @@ +""" +Setup script for HTML.py +""" + +import distutils.core +import HTML + +DESCRIPTION = """A Python module to easily generate HTML code (tables, lists, ...). +See http://www.decalage.info/python/html for more information. +""" + +kw = { + 'name': "HTML.py", + 'version': HTML.__version__, + 'description': DESCRIPTION, + 'author': "Philippe Lagadec", + 'author_email': "decalage (a) laposte.net", + 'url': "http://www.decalage.info/python/html", + 'license': "CeCILL (open-source GPL compatible)", + 'py_modules': ['HTML'] + } + + +# If we're running Python 2.3+, add extra information +if hasattr(distutils.core, 'setup_keywords'): + if 'classifiers' in distutils.core.setup_keywords: + kw['classifiers'] = [ + 'Development Status :: 4 - Beta', + #'License :: Freely Distributable', + 'Natural Language :: English', + 'Intended Audience :: Developers', + 'Topic :: Internet :: WWW/HTTP', + 'Operating System :: OS Independent', + 'Programming Language :: Python', + 'Topic :: Software Development :: Libraries :: Python Modules' + ] + if 'download_url' in distutils.core.setup_keywords: + kw['download_url'] = "http://www.decalage.info/python/html" + + +distutils.core.setup(**kw) diff --git a/INSTALL_WINDOWS b/INSTALL_WINDOWS new file mode 100644 index 0000000..0b1052e --- /dev/null +++ b/INSTALL_WINDOWS @@ -0,0 +1,22 @@ +dépendances : +R 2.10.1 +http://cran.cict.fr/bin/windows/ + +python 2.6 +http://www.python.org/ + +Numpy +http://surfnet.dl.sourceforge.net/sourceforge/numpy/ + +wxpython +http://surfnet.dl.sourceforge.net/sourceforge/wxpython/ + +xlrd + +package de R : +rgl +ca +gee +ape +igraph +proxy diff --git a/KeyFrame.py b/KeyFrame.py new file mode 100755 index 0000000..2c40909 --- /dev/null +++ b/KeyFrame.py @@ -0,0 +1,160 @@ +# -*- coding: utf-8 -*- +#Author: Pierre Ratinaud +#Copyright (c) 2008 Pierre Ratinaud +#Lisense: GNU/GPL + +import wx +from functions import sortedby + +# begin wxGlade: extracode +# end wxGlade + +class AlcOptFrame(wx.Dialog): + def __init__(self,parent, *args, **kwds): + # begin wxGlade: AlcOptFrame.__init__ + kwds["style"] = wx.DEFAULT_DIALOG_STYLE + wx.Dialog.__init__(self, *args, **kwds) + self.cle={ + 'adj_sup': [wx.NewId(),wx.NewId(),u"Adjectif supplémentaire"], + 'art_ind': [wx.NewId(),wx.NewId(),u"Article indéfini"], + 'adj_pos': [wx.NewId(),wx.NewId(),u"Adjectif possessif"], + 'adv_sup': [wx.NewId(),wx.NewId(),u"Adverbe supplémentaire"], + 'pro_dem': [wx.NewId(),wx.NewId(),u"Pronom démonstratif"], + 'art_def': [wx.NewId(),wx.NewId(),u"Article défini"], + 'con': [wx.NewId(),wx.NewId(),u"Conjonction"], + 'pre': [wx.NewId(),wx.NewId(),u"Préposition"], + 'ono': [wx.NewId(),wx.NewId(),u"Onomatopée"], + 'adj_dem': [wx.NewId(),wx.NewId(),u"Adjectif démonstratif"], + 'nom_sup': [wx.NewId(),wx.NewId(),u"Nom supplémentaire"], + 'adv': [wx.NewId(),wx.NewId(),u"Adverbe"], + 'pro_per': [wx.NewId(),wx.NewId(),u"Pronom personnel"], + 'ver': [wx.NewId(),wx.NewId(),u"Verbe"], + 'adj_num': [wx.NewId(),wx.NewId(),u"Adjectif numérique"], + 'pro_rel': [wx.NewId(),wx.NewId(),u"Pronom relatif"], + 'adj_ind': [wx.NewId(),wx.NewId(),u"Adjectif indéfini"], + 'pro_ind': [wx.NewId(),wx.NewId(),u"Pronom indéfini"], + 'pro_pos': [wx.NewId(),wx.NewId(),u"Pronom possessif"], + 'aux': [wx.NewId(),wx.NewId(),u"Auxiliaire"], + 'ver_sup': [wx.NewId(),wx.NewId(),u"Verbe supplémentaire"], + 'adj': [wx.NewId(),wx.NewId(),u"Adjectif"], + 'adj_int': [wx.NewId(),wx.NewId(),u"Adjectif interrogatif"], + 'nom': [wx.NewId(),wx.NewId(),u"Nom commun"], + 'num' : [wx.NewId(),wx.NewId(),u"Chiffre"], + 'nr' : [wx.NewId(),wx.NewId(),u"Formes non reconnues"], + } + self.parent=parent + self.KeyConf=self.parent.KeyConf + self.listlabel=[] + self.listspin=[] + self.listbutton=[] + self.listcle=[] + self.listids=[] + self.listidb=[] + + self.label_1 = wx.StaticText(self, -1, u" Choix des clés d'analyse\n0=éliminé ; 1=active ; 2=supplémentaire\n") + self.listcleori=[[cle]+self.cle[cle] for cle in self.cle] + self.listcleori=sortedby(self.listcleori,1,3) + + for line in self.listcleori: + cle,ids,idb,label=line + self.listlabel.append(wx.StaticText(self, -1, label)) + self.listspin.append(wx.SpinCtrl(self, ids,self.KeyConf.get('KEYS',cle), min=0, max=2)) + #if cle != 'nr' and cle!= 'num' : + self.listbutton.append(wx.Button(self, idb, u"voir liste")) + self.listids.append(ids) + self.listidb.append(idb) + self.listcle.append(cle) + + + self.button_val = wx.Button(self,wx.ID_APPLY) + + for button in self.listbutton : + self.Bind(wx.EVT_BUTTON,self.OnShowList,button) + + self.Bind(wx.EVT_BUTTON, self.OnApply, self.button_val) + + self.dico=self.parent.parent.lexique#'dictionnaires/lexique.txt') + + self.__set_properties() + self.__do_layout() + # end wxGlade + + def __set_properties(self): + # begin wxGlade: AlcOptFrame.__set_properties + self.SetTitle(u"Clés d'analyse") + # end wxGlade + + def __do_layout(self): + # begin wxGlade: AlcOptFrame.__do_layout + sizer_1 = wx.BoxSizer(wx.VERTICAL) + sizer_2 = wx.BoxSizer(wx.VERTICAL) + sizer_3 = wx.BoxSizer(wx.HORIZONTAL) + grid_sizer_1 = wx.GridSizer(14, 3, 0, 0) + grid_sizer_2 = wx.GridSizer(14, 3, 0, 0) + sizer_2.Add(self.label_1, 0, wx.ALIGN_CENTER_HORIZONTAL|wx.ALIGN_CENTER_VERTICAL, 0) + for i in range(0,14): + grid_sizer_1.Add(self.listlabel[i], 0, wx.ALIGN_CENTER_HORIZONTAL|wx.ALIGN_CENTER_VERTICAL, 0) + grid_sizer_1.Add(self.listspin[i], 0, wx.ALIGN_CENTER_HORIZONTAL|wx.ALIGN_CENTER_VERTICAL, 0) + grid_sizer_1.Add(self.listbutton[i], 0, wx.ALIGN_CENTER_HORIZONTAL|wx.ALIGN_CENTER_VERTICAL, 0) + for i in range(13,len(self.listlabel)): + grid_sizer_2.Add(self.listlabel[i], 0, wx.ALIGN_CENTER_HORIZONTAL|wx.ALIGN_CENTER_VERTICAL, 0) + grid_sizer_2.Add(self.listspin[i], 0, wx.ALIGN_CENTER_HORIZONTAL|wx.ALIGN_CENTER_VERTICAL, 0) + grid_sizer_2.Add(self.listbutton[i], 0, wx.ALIGN_CENTER_HORIZONTAL|wx.ALIGN_CENTER_VERTICAL, 0) + sizer_3.Add(grid_sizer_1, 1, wx.EXPAND, 0) + sizer_3.Add(grid_sizer_2, 1, wx.EXPAND, 0) + sizer_2.Add(sizer_3, 1, wx.EXPAND, 8) + sizer_2.Add(self.button_val,0, wx.ALIGN_CENTER_HORIZONTAL, 0) + sizer_1.Add(sizer_2, 1, wx.EXPAND, 0) + self.SetSizer(sizer_1) + sizer_1.Fit(self) + self.Layout() + # end wxGlade + + def OnShowList(self,evt): + id=evt.GetEventObject().GetId() + pos=self.listidb.index(id) + type=self.listcle[pos] + self.CreateList(type) + + def CreateList(self,type): + if type=='ver_sup' or type=='ver': + liste=[descr[0] for item,descr in self.dico.iteritems() if descr[1]==type] + liste=list(set(liste)) + else: + liste=[item for item,descr in self.dico.iteritems() if descr[1]==type] + liste.sort() + txt=('\n').join(liste) + ListViewFrame=ListView(self.parent.parent) + ListViewFrame.text_ctrl_1.WriteText(txt) + ListViewFrame.text_ctrl_1.SetSelection(0,0) + ListViewFrame.text_ctrl_1.SetInsertionPoint(0) + ListViewFrame.CenterOnParent() + val=ListViewFrame.ShowModal() + + def OnApply(self,evt): + for i in range(0,len(self.listlabel)): + self.KeyConf.set('KEYS',self.listcle[i],`self.listspin[i].GetValue()`) + self.Destroy() + + +class ListView(wx.Dialog): + def __init__(self, parent): + wx.Dialog.__init__(self, parent, size=wx.Size(200, 400),style=wx.DEFAULT_DIALOG_STYLE) + self.text_ctrl_1 = wx.TextCtrl(self, -1, "", style=wx.TE_MULTILINE|wx.TE_RICH2) + self.text_ctrl_1.SetMinSize(wx.Size(200, 400)) + self.btn = wx.Button(self, wx.ID_OK) + self.SetMinSize(wx.Size(200, 400)) + self.__set_properties() + self.__do_layout() + + def __set_properties(self): + self.SetTitle("Liste") + + def __do_layout(self): + sizer_1 = wx.BoxSizer(wx.VERTICAL) + sizer_1.Add(self.text_ctrl_1, 1, wx.EXPAND, 0) + sizer_1.Add(self.btn,0,wx.EXPAND,0) + self.SetSizer(sizer_1) + sizer_1.Fit(self) + self.Layout() + diff --git a/Liste.py b/Liste.py new file mode 100644 index 0000000..86ec3bf --- /dev/null +++ b/Liste.py @@ -0,0 +1,315 @@ +# -*- coding: utf-8 -*- + +#---------------------------------------------------------------------------- +# Name: ListCtrl.py +# Author: Pierre Ratinaud +# + +#comes from ListCtrl.py from the demo tool of wxPython: +# Author: Robin Dunn & Gary Dumer +# +# Created: +# Copyright: (c) 1998 by Total Control Software +# Licence: wxWindows license +#---------------------------------------------------------------------------- + +import os +import sys +import wx +from dialog import SearchDial +import wx.lib.mixins.listctrl as listmix +import cStringIO + +#--------------------------------------------------------------------------- + +class List(wx.ListCtrl, listmix.ListCtrlAutoWidthMixin): + def __init__(self, parent, ID, pos=wx.DefaultPosition, + size=wx.DefaultSize, style=0): + wx.ListCtrl.__init__(self, parent, ID, pos, size, style) + listmix.ListCtrlAutoWidthMixin.__init__(self) + + +class ListPanel(wx.Panel, listmix.ColumnSorterMixin): + def __init__(self, parent, gparent, dlist): + self.parent = parent + self.gparent = gparent + self.source = gparent + self.dlist = dlist + wx.Panel.__init__(self, parent, -1, style=wx.WANTS_CHARS) + + search_id = wx.NewId() + self.parent.Bind(wx.EVT_MENU, self.onsearch, id = search_id) + self.accel_tbl = wx.AcceleratorTable([(wx.ACCEL_CTRL, ord('F'), search_id)]) + self.SetAcceleratorTable(self.accel_tbl) + + self.il = wx.ImageList(16, 16) + self.sm_up = self.il.Add(getSmallUpArrowBitmap()) + self.sm_dn = self.il.Add(getSmallDnArrowBitmap()) + + + tID = wx.NewId() + + self.list = List(self, tID, + style=wx.LC_REPORT + | wx.BORDER_NONE + | wx.LC_EDIT_LABELS + | wx.LC_SORT_ASCENDING + ) + + + self.list.SetImageList(self.il, wx.IMAGE_LIST_SMALL) + self.PopulateList(dlist) + + self.Bind(wx.EVT_SIZE, self.OnSize) + + self.Bind(wx.EVT_LIST_ITEM_SELECTED, self.OnItemSelected, self.list) + self.Bind(wx.EVT_LIST_COL_CLICK, self.OnColClick, self.list) + # for wxMSW + self.list.Bind(wx.EVT_COMMAND_RIGHT_CLICK, self.OnRightClick) + + # for wxGTK + self.list.Bind(wx.EVT_RIGHT_UP, self.OnRightClick) + self.itemDataMap = dlist + + listmix.ColumnSorterMixin.__init__(self, 3) + self.SortListItems(1, False) + self.do_greyline() +#----------------------------------------------------------------------------------------- + + def PopulateList(self, dlist): + + #self.list.InsertColumn(0,'id', wx.LIST_FORMAT_LEFT) +# i=1 + self.list.InsertColumn(0, 'forme', wx.LIST_FORMAT_RIGHT) + self.list.InsertColumn(1, 'nb', wx.LIST_FORMAT_RIGHT) + self.list.InsertColumn(2, 'type', wx.LIST_FORMAT_RIGHT) + self.list.InsertColumn(3, '', wx.LIST_FORMAT_RIGHT) + + ct = 0 + for key, data in dlist.iteritems(): + ct += 1 + index = self.list.InsertStringItem(sys.maxint, data[0]) + self.list.SetStringItem(index, 1, `data[1]`) + self.list.SetStringItem(index, 2, data[2]) + self.list.SetStringItem(index, 3, '') + self.list.SetItemData(index, key) + + self.list.SetColumnWidth(0, 150) + self.list.SetColumnWidth(1, 100) + self.list.SetColumnWidth(2, 100) + self.list.SetColumnWidth(3, wx.LIST_AUTOSIZE) + + + self.currentItem = 0 + + def do_greyline(self): + for row in xrange(self.list.GetItemCount()): + if row % 2 : + self.list.SetItemBackgroundColour(row, (230, 230, 230)) + else : + self.list.SetItemBackgroundColour(row, wx.WHITE) + + def OnColClick(self, event): + self.do_greyline() + + # Used by the ColumnSorterMixin, see wx/lib/mixins/listctrl.py + def GetListCtrl(self): + return self.list + + # Used by the ColumnSorterMixin, see wx/lib/mixins/listctrl.py + def GetSortImages(self): + return (self.sm_dn, self.sm_up) + + + def OnRightDown(self, event): + x = event.GetX() + y = event.GetY() + item, flags = self.list.HitTest((x, y)) + + if flags & wx.LIST_HITTEST_ONITEM: + self.list.Select(item) + + event.Skip() + + + def getColumnText(self, index, col): + item = self.list.GetItem(index, col) + return item.GetText() + + + def OnItemSelected(self, event): + self.currentItem = event.m_itemIndex + event.Skip() + + def onsearch(self, evt) : + self.dial = SearchDial(self, self, 0, True) + self.dial.CenterOnParent() + self.dial.ShowModal() + self.dial.Destroy() + + def OnRightClick(self, event): + + # only do this part the first time so the events are only bound once + if not hasattr(self, "popupID1"): + self.popupID1 = wx.NewId() + self.popupID2 = wx.NewId() + # self.popupID3 = wx.NewId() + + self.Bind(wx.EVT_MENU, self.OnPopupOne, id=self.popupID1) + self.Bind(wx.EVT_MENU, self.OnPopupTwo, id=self.popupID2) +# self.Bind(wx.EVT_MENU, self.OnPopupThree, id=self.popupID3) + + # make a menu + menu = wx.Menu() + # add some items + menu.Append(self.popupID1, u"Formes associées") + menu.Append(self.popupID2, u"Concordancier") +# menu.Append(self.popupID3, "recharger") + + self.PopupMenu(menu) + menu.Destroy() + + + def OnPopupOne(self, event): + corpus = self.gparent.corpus + word = self.getColumnText(self.list.GetFirstSelected(), 0) + lems = corpus.getlems() + rep = [] + for forme in lems[word].formes : + rep.append([corpus.getforme(forme).forme, corpus.getforme(forme).freq]) + win = message(self, -1, u"Formes associées", size=(300, 200), style=wx.DEFAULT_FRAME_STYLE) + win.html = '\n' + '
    '.join([' : '.join([str(val) for val in forme]) for forme in rep]) + '\n' + win.HtmlPage.SetPage(win.html) + win.Show(True) + + def OnPopupTwo(self, event): + corpus = self.gparent.corpus + win = message(self, -1, u"Concordancier", size=(600, 200), style=wx.DEFAULT_FRAME_STYLE) + avap = 60 + item = self.getColumnText(self.list.GetFirstSelected(), 0) + listmot = corpus.getlems()[item].formes + #uce_ok = [corpus.formes[corpus.idformes[forme].forme][1] for forme in listmot] + uce_ok = corpus.getlemuces(item)#list(set([tuple(val) for line in uce_ok for val in line])) + txt = '

    Concordancier

    ' + res = corpus.getconcorde(uce_ok) + for uce in res : + ucetxt = ' '+uce[1]+' ' + txt += ' '.join(corpus.ucis[corpus.getucefromid(uce[0]).uci].etoiles) + '
    ' + for forme in listmot : + forme = corpus.getforme(forme).forme + ucetxt = ucetxt.replace(' '+forme+' ', ' ' + forme + ' ') + txt += ucetxt + '

    ' +# for uce in uce_ok: +# content = ' '+' '.join(corpus.ucis_paras_uces[uce[0]][uce[1]][uce[2]])+' ' +# for form in listmot : +# sp = '' +# i = 0 +# forme = ' ' + form + ' ' +# while i < len(content): +# coordword = content[i:].find(forme) +# if coordword != -1 and i == 0: +# txt += '
    ' + ' '.join(corpus.ucis[uce[0]][0]) + '
    ' +# if coordword < avap: +# sp = ' ' * (avap - coordword) +# deb = i +# else: +# deb = i + coordword - avap +# if len(content) < i + coordword + avap: +# fin = len(content) - 1 +# else: +# fin = i + coordword + avap +# txt += '' + sp + content[deb:fin].replace(forme, '' + forme + '') + '
    ' +# i += coordword + len(forme) +# sp = '' +# elif coordword != -1 and i != 0 : +# if coordword < avap: +# sp = ' ' * (avap - coordword) +# deb = i +# else: +# deb = i + coordword - avap +# if len(content) < i + coordword + avap: +# fin = len(content) - 1 +# else: +# fin = i + coordword + avap +# txt += '' + sp + content[deb:fin].replace(forme, '' + forme + '') + '
    ' +# i += coordword + len(forme) +# sp = '' +# else: +# i = len(content) +# sp = '' + win.HtmlPage.SetPage(txt) + win.Show(True) + + def OnSize(self, event): + w, h = self.GetClientSizeTuple() + self.list.SetDimensions(0, 0, w, h) + +class message(wx.Frame): + def __init__(self, *args, **kwds): + # begin wxGlade: MyFrame.__init__ + kwds["style"] = wx.DEFAULT_FRAME_STYLE + wx.Frame.__init__(self, *args, **kwds) + #self.text_ctrl_1 = wx.TextCtrl(self, -1, "", style=wx.TE_MULTILINE) + self.HtmlPage = wx.html.HtmlWindow(self, -1) + if "gtk2" in wx.PlatformInfo: + self.HtmlPage.SetStandardFonts() + self.HtmlPage.SetFonts('Courier', 'Courier') + + + self.button_1 = wx.Button(self, -1, "Fermer") + self.Bind(wx.EVT_BUTTON, self.OnCloseMe, self.button_1) + self.Bind(wx.EVT_CLOSE, self.OnCloseWindow) + self.__do_layout() + # end wxGlade + + def __do_layout(self): + # begin wxGlade: MyFrame.__do_layout + sizer_1 = wx.BoxSizer(wx.VERTICAL) + sizer_2 = wx.BoxSizer(wx.VERTICAL) + sizer_2.Add(self.HtmlPage, 1, wx.EXPAND | wx.ADJUST_MINSIZE, 0) + sizer_2.Add(self.button_1, 0, wx.ALIGN_CENTER_HORIZONTAL | wx.ADJUST_MINSIZE, 0) + sizer_1.Add(sizer_2, 1, wx.EXPAND, 0) + self.SetAutoLayout(True) + self.SetSizer(sizer_1) + self.Layout() + # end wxGlade + + def OnCloseMe(self, event): + self.Close(True) + + def OnCloseWindow(self, event): + self.Destroy() + + +def getSmallUpArrowData(): + return \ +'\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x10\x00\x00\x00\x10\x08\x06\ +\x00\x00\x00\x1f\xf3\xffa\x00\x00\x00\x04sBIT\x08\x08\x08\x08|\x08d\x88\x00\ +\x00\x00C\xb0\x89\ +\xd3.\x10\xd1m\xc3\xe5*\xbc.\x80i\xc2\x17.\x8c\xa3y\x81\x01\x00\xa1\x0e\x04e\ +?\x84B\xef\x00\x00\x00\x00IEND\xaeB`\x82" + +def getSmallDnArrowBitmap(): + return wx.BitmapFromImage(getSmallDnArrowImage()) + +def getSmallDnArrowImage(): + stream = cStringIO.StringIO(getSmallDnArrowData()) + return wx.ImageFromStream(stream) diff --git a/OptionAlceste.py b/OptionAlceste.py new file mode 100755 index 0000000..ea3ba80 --- /dev/null +++ b/OptionAlceste.py @@ -0,0 +1,306 @@ +# -*- coding: utf-8 -*- +#Author: Pierre Ratinaud +#Copyright (c) 2008-2009 Pierre Ratinaud +#Lisense: GNU/GPL + +import wx +import shutil +from KeyFrame import AlcOptFrame +from chemins import ConstructConfigPath +from functions import DoConf + + + +class OptionAlc(wx.Dialog): + def __init__(self, parent, parametres, *args, **kwds): + kwds['style'] = wx.DEFAULT_DIALOG_STYLE + wx.Dialog.__init__(self, parent, *args, **kwds) + self.parent = parent + self.parametres = parametres + self.DictPath = parametres['pathout'] + self.AlcesteConf = parametres + self.choose = False + + self.label_1 = wx.StaticText(self, -1, u"Lemmatisation") + self.radio_1 = wx.RadioBox(self, -1, u"", choices=['oui', 'non'], majorDimension=0, style=wx.RA_SPECIFY_ROWS) + + self.label_12 = wx.StaticText(self, -1, u"Classification") + self.radio_box_2 = wx.RadioBox(self, -1, u"", choices=[u"double sur UC", u"simple sur UCE", u"simple sur UCI"], majorDimension=0, style=wx.RA_SPECIFY_ROWS) #, u"simple sur UCE (non implemente)" + self.label_2 = wx.StaticText(self, -1, u"taille uc 1") + self.spin_ctrl_1 = wx.SpinCtrl(self, -1, "formes actives",size = (100,30), min=0, max=100) + self.label_3 = wx.StaticText(self, -1, u"taille uc 2") + self.spin_ctrl_2 = wx.SpinCtrl(self, -1, "",size = (100,30), min=0, max=100) + self.lab_nbcl = wx.StaticText(self, -1, u"nombre de classes terminales de la phase 1") + self.spin_nbcl = wx.SpinCtrl(self, -1, "",size = (100,30), min=2, max=100) + txt = """Nombre minimum d'uce par classe +(0 = automatique)""" + self.label_7 = wx.StaticText(self, -1, txt) + self.spin_ctrl_4 = wx.SpinCtrl(self, -1, "",size = (100,30), min=0, max=1000) + txt = u"""Fréquence minimum d'une forme +analysée (2 = automatique)""" + self.label_8 = wx.StaticText(self, -1, txt) + self.spin_ctrl_5 = wx.SpinCtrl(self, -1, "",size = (100,30), min=2, max=1000) + self.label_max_actives = wx.StaticText(self, -1, u"Nombre maximum de formes analysées") + self.spin_max_actives = wx.SpinCtrl(self, -1, "",size = (100,30), min=20, max=10000) + self.label_4 = wx.StaticText(self, -1, u"Configuration \ndes clés d'analyse") + self.button_5 = wx.Button(self, wx.ID_PREFERENCES, "") + self.button_1 = wx.Button(self, wx.ID_CANCEL, "") + self.button_2 = wx.Button(self, wx.ID_DEFAULT, u"Valeurs par défaut") + self.button_4 = wx.Button(self, wx.ID_OK, "") + self.static_line_1 = wx.StaticLine(self, -1) + + self.__set_properties() + self.__do_layout() + + self.Bind(wx.EVT_BUTTON, self.OnKeyPref, self.button_5) + self.Bind(wx.EVT_BUTTON, self.OnDef, self.button_2) + + def __set_properties(self): + self.SetTitle("Options") + #lang = self.AlcesteConf.get('ALCESTE', 'lang') + #self.choice_dict.SetSelection(self.langues.index(lang)) + DefaultLem = self.parametres['lem'] + if DefaultLem : + self.radio_1.SetSelection(0) + else: + self.radio_1.SetSelection(1) + self.radio_box_2.SetSelection(int(self.parametres['classif_mode'])) + self.spin_ctrl_1.SetValue(int(self.parametres['tailleuc1'])) + self.spin_ctrl_2.SetValue(int(self.parametres['tailleuc2'])) + self.spin_ctrl_4.SetValue(int(self.parametres['mincl'])) + self.spin_ctrl_5.SetValue(int(self.parametres['minforme'])) + self.spin_ctrl_5.Disable() + self.spin_max_actives.SetValue(int(self.parametres['max_actives'])) + self.spin_nbcl.SetValue(int(self.parametres['nbcl_p1'])) + + def __do_layout(self): + sizer_1 = wx.BoxSizer(wx.VERTICAL) + sizer_2 = wx.BoxSizer(wx.VERTICAL) + grid_sizer2 = wx.FlexGridSizer(15, 2, 0, 0) + grid_button = wx.FlexGridSizer(1, 3, 0, 0) + + #grid_sizer2.Add(self.label_dict, 0, wx.ALIGN_LEFT | wx.ALIGN_CENTER_VERTICAL, 0) + #grid_sizer2.Add(self.choice_dict, 0, wx.ALIGN_LEFT | wx.ALIGN_CENTER_VERTICAL, 0) + + grid_sizer2.Add(self.label_1, 0, wx.ALIGN_LEFT | wx.ALIGN_CENTER_VERTICAL, 0) + grid_sizer2.Add(self.radio_1, 0, wx.ALIGN_LEFT | wx.ALIGN_CENTER_VERTICAL, 0) + grid_sizer2.Add(wx.StaticLine(self), 0, wx.EXPAND | wx.ALL, 1) + grid_sizer2.Add(wx.StaticLine(self, -1), 0, wx.EXPAND | wx.ALL, 1) + + grid_sizer2.Add(self.label_12, 0, wx.ALIGN_LEFT | wx.ALIGN_CENTER_VERTICAL, 0) + grid_sizer2.Add(self.radio_box_2, 0, wx.ALIGN_LEFT | wx.ALIGN_CENTER_VERTICAL, 0) + grid_sizer2.Add(wx.StaticLine(self), 0, wx.EXPAND | wx.ALL, 1) + grid_sizer2.Add(wx.StaticLine(self, -1), 0, wx.EXPAND | wx.ALL, 1) + + grid_sizer2.Add(self.label_2, 0, wx.ALIGN_LEFT | wx.ALIGN_CENTER_VERTICAL, 0) + grid_sizer2.Add(self.spin_ctrl_1, 0, wx.ALIGN_LEFT | wx.ALIGN_CENTER_VERTICAL, 0) + grid_sizer2.Add(wx.StaticLine(self), 0, wx.EXPAND | wx.ALL, 1) + grid_sizer2.Add(wx.StaticLine(self, -1), 0, wx.EXPAND | wx.ALL, 1) + + grid_sizer2.Add(self.label_3, 0, wx.ALIGN_LEFT | wx.ALIGN_CENTER_VERTICAL, 0) + grid_sizer2.Add(self.spin_ctrl_2, 0, wx.ALIGN_LEFT | wx.ALIGN_CENTER_VERTICAL, 0) + grid_sizer2.Add(wx.StaticLine(self), 0, wx.EXPAND | wx.ALL, 1) + grid_sizer2.Add(wx.StaticLine(self, -1), 0, wx.EXPAND | wx.ALL, 1) + + grid_sizer2.Add(self.lab_nbcl, 0, wx.ALIGN_LEFT | wx.ALIGN_CENTER_VERTICAL, 0) + grid_sizer2.Add(self.spin_nbcl, 0, wx.ALIGN_LEFT | wx.ALIGN_CENTER_VERTICAL, 0) + grid_sizer2.Add(wx.StaticLine(self), 0, wx.EXPAND | wx.ALL, 1) + grid_sizer2.Add(wx.StaticLine(self, -1), 0, wx.EXPAND | wx.ALL, 1) + + grid_sizer2.Add(self.label_7, 0, wx.ALIGN_LEFT | wx.ALIGN_CENTER_VERTICAL, 0) + grid_sizer2.Add(self.spin_ctrl_4, 0, wx.ALIGN_LEFT | wx.ALIGN_CENTER_VERTICAL, 0) + grid_sizer2.Add(wx.StaticLine(self), 0, wx.EXPAND | wx.ALL, 1) + grid_sizer2.Add(wx.StaticLine(self, -1), 0, wx.EXPAND | wx.ALL, 1) + + grid_sizer2.Add(self.label_8, 0, wx.ALIGN_LEFT | wx.ALIGN_CENTER_VERTICAL, 0) + grid_sizer2.Add(self.spin_ctrl_5, 0, wx.ALIGN_LEFT | wx.ALIGN_CENTER_VERTICAL, 0) + grid_sizer2.Add(wx.StaticLine(self), 0, wx.EXPAND | wx.ALL, 1) + grid_sizer2.Add(wx.StaticLine(self, -1), 0, wx.EXPAND | wx.ALL, 1) + + grid_sizer2.Add(self.label_max_actives, 0, wx.ALIGN_LEFT | wx.ALIGN_CENTER_VERTICAL, 0) + grid_sizer2.Add(self.spin_max_actives, 0, wx.ALIGN_LEFT | wx.ALIGN_CENTER_VERTICAL, 0) + grid_sizer2.Add(wx.StaticLine(self), 0, wx.EXPAND | wx.ALL, 1) + grid_sizer2.Add(wx.StaticLine(self, -1), 0, wx.EXPAND | wx.ALL, 1) + + grid_sizer2.Add(self.label_4, 0, wx.ALIGN_LEFT | wx.ALIGN_CENTER_VERTICAL, 0) + grid_sizer2.Add(self.button_5, 0, wx.ALIGN_LEFT | wx.ALIGN_CENTER_VERTICAL, 0) + grid_sizer2.Add(wx.StaticLine(self), 0, wx.EXPAND | wx.ALL, 1) + grid_sizer2.Add(wx.StaticLine(self, -1), 0, wx.EXPAND | wx.ALL, 1) + + grid_button.Add(self.button_1, 0, wx.ALIGN_CENTER_HORIZONTAL, 0) + grid_button.Add(self.button_2, 0, wx.ALIGN_CENTER_HORIZONTAL, 0) + grid_button.Add(self.button_4, 0, wx.ALIGN_CENTER_HORIZONTAL, 0) + sizer_2.Add(grid_sizer2, 3, wx.EXPAND, 0) + sizer_2.Add(grid_button, 0, wx.ALIGN_CENTER_HORIZONTAL | wx.ALIGN_CENTER_VERTICAL, 0) + sizer_1.Add(sizer_2, 0, wx.EXPAND, 0) + self.SetSizer(sizer_1) + sizer_1.Fit(self) + self.Layout() + + def OnKeyPref(self, event): + self.choose = True + dial = AlcOptFrame(self.parent, self) + dial.CenterOnParent() + val = dial.ShowModal() + + def OnDef(self, event): + ConfOri = ConstructConfigPath(self.parent.AppliPath, user=False) + ConfUser = ConstructConfigPath(self.parent.UserConfigPath) + shutil.copyfile(ConfOri['alceste'], ConfUser['alceste']) + corpus = self.parametres['corpus'] + pathout = self.parametres['pathout'] + self.parametres = DoConf(self.parent.ConfigPath['alceste']).getoptions('ALCESTE') + self.parametres['corpus'] = corpus + self.parametres['pathout'] = pathout + self.__set_properties() + +###################################################################################@ + +class OptionPam(wx.Dialog): + def __init__(self, parent, *args, **kwds): + kwds['style'] = wx.DEFAULT_DIALOG_STYLE + wx.Dialog.__init__(self, *args, **kwds) + self.parent = parent + self.DictPath = parent.DictPath + self.pamconf = parent.pamconf + self.type = parent.type + self.choose = False + + self.label_1 = wx.StaticText(self, -1, u"Lemmatisation") + self.radio_1 = wx.RadioBox(self, -1, u"", choices=['oui', 'non'], majorDimension=0, style=wx.RA_SPECIFY_ROWS) + self.label_exp = wx.StaticText(self, -1, u"Utiliser le dict. des expressions") + self.radio_exp = wx.RadioBox(self, -1, u"", choices=['oui', 'non'], majorDimension=0, style=wx.RA_SPECIFY_ROWS) + txt = u"""Methode de construction +de la matrice des distances""" + self.label_12 = wx.StaticText(self, -1, txt) + self.distance = [u"binary", u"euclidean", u"maximum", u'manhattan', u'canberra', u'minkowski'] + self.choice_1 = wx.Choice(self, -1, (100,50), choices=self.distance) + self.label_13 = wx.StaticText(self, -1, u'Analyse') + self.cltype = [u'k-means (pam)', u'fuzzy (fanny)'] + self.radio_box_3 = wx.RadioBox(self, -1, u"", choices=self.cltype, majorDimension=0, style=wx.RA_SPECIFY_ROWS) + self.label_classif = wx.StaticText(self, -1, u"Classification") + self.radio_box_classif = wx.RadioBox(self, -1, u"", choices=[u"sur UCE", u"sur UCI"], majorDimension=0, style=wx.RA_SPECIFY_ROWS) + #self.label_2 = wx.StaticText(self, -1, "taille uc") + #self.spin_ctrl_1 = wx.SpinCtrl(self, -1, "formes actives", min=0, max=100) + self.label_max_actives = wx.StaticText(self, -1, u"Nombre maximum de formes analysées") + self.spin_max_actives = wx.SpinCtrl(self, -1, "",size = (100,30), min=20, max=10000) + txt = """Nombre de formes par uce +(0 = automatique)""" + self.label_6 = wx.StaticText(self, -1, txt) + self.spin_ctrl_3 = wx.SpinCtrl(self, -1, "", size = (100,30), min=0, max=100000) + txt = "Nombre de classes" + self.label_7 = wx.StaticText(self, -1, txt) + self.spin_ctrl_4 = wx.SpinCtrl(self, -1, "", size = (100,30), min=0, max=1000) + self.label_4 = wx.StaticText(self, -1, u"Configuration \ndes clés d'analyse") + self.button_5 = wx.Button(self, wx.ID_PREFERENCES, "") + self.button_1 = wx.Button(self, wx.ID_CANCEL, "") + self.button_2 = wx.Button(self, wx.ID_DEFAULT, u"Valeurs par défaut") + self.button_4 = wx.Button(self, wx.ID_OK, "") + self.static_line_1 = wx.StaticLine(self, -1) + + self.__set_properties() + self.__do_layout() + + self.Bind(wx.EVT_BUTTON, self.OnKeyPref, self.button_5) + self.Bind(wx.EVT_BUTTON, self.OnDef, self.button_2) + + def __set_properties(self): + self.SetTitle("Options") + DefaultLem = self.pamconf.getboolean('pam', 'lem') + if DefaultLem : + self.radio_1.SetSelection(0) + else: + self.radio_1.SetSelection(1) + expressions = self.pamconf.getboolean('pam', 'expressions') + if expressions : + self.radio_exp.SetSelection(0) + else : + self.radio_exp.SetSelection(1) + self.choice_1.SetSelection(self.distance.index(self.pamconf.get('pam', 'method'))) + if self.pamconf.get('pam', 'cluster_type') == u'pam' : + self.radio_box_3.SetSelection(0) + else : + self.radio_box_3.SetSelection(1) + self.radio_box_classif.SetSelection(int(self.pamconf.get('pam','type'))) + self.spin_max_actives.SetValue(int(self.pamconf.get('pam','max_actives'))) + self.spin_ctrl_3.SetValue(int(self.pamconf.get('pam', 'nbforme_uce'))) + cle = 'nbcl' + self.spin_ctrl_4.SetValue(int(self.pamconf.get('pam', cle))) + + def __do_layout(self): + sizer_1 = wx.BoxSizer(wx.VERTICAL) + sizer_2 = wx.BoxSizer(wx.VERTICAL) + grid_sizer2 = wx.FlexGridSizer(11, 2, 2, 2) + grid_button = wx.FlexGridSizer(1, 3, 1, 1) + grid_sizer2.Add(self.label_1, 0, wx.ALIGN_LEFT | wx.ALIGN_CENTER_VERTICAL, 0) + grid_sizer2.Add(self.radio_1, 0, wx.ALIGN_LEFT | wx.ALIGN_CENTER_VERTICAL, 0) + grid_sizer2.Add(wx.StaticLine(self), 0, wx.EXPAND | wx.ALL, 1) + grid_sizer2.Add(wx.StaticLine(self, -1), 0, wx.EXPAND | wx.ALL, 1) + + grid_sizer2.Add(self.label_exp, 0, wx.ALIGN_LEFT | wx.ALIGN_CENTER_VERTICAL, 0) + grid_sizer2.Add(self.radio_exp, 0, wx.ALIGN_LEFT | wx.ALIGN_CENTER_VERTICAL, 0) + grid_sizer2.Add(wx.StaticLine(self), 0, wx.EXPAND | wx.ALL, 1) + grid_sizer2.Add(wx.StaticLine(self, -1), 0, wx.EXPAND | wx.ALL, 1) + + grid_sizer2.Add(self.label_12, 0, wx.ALIGN_LEFT | wx.ALIGN_CENTER_VERTICAL, 0) + grid_sizer2.Add(self.choice_1, 0, wx.ALIGN_LEFT | wx.ALIGN_CENTER_VERTICAL, 0) + grid_sizer2.Add(wx.StaticLine(self), 0, wx.EXPAND | wx.ALL, 1) + grid_sizer2.Add(wx.StaticLine(self, -1), 0, wx.EXPAND | wx.ALL, 1) + + grid_sizer2.Add(self.label_13, 0, wx.ALIGN_LEFT | wx.ALIGN_CENTER_VERTICAL, 0) + grid_sizer2.Add(self.radio_box_3, 0, wx.ALIGN_LEFT | wx.ALIGN_CENTER_VERTICAL, 0) + grid_sizer2.Add(wx.StaticLine(self), 0, wx.EXPAND | wx.ALL, 1) + grid_sizer2.Add(wx.StaticLine(self, -1), 0, wx.EXPAND | wx.ALL, 1) + + grid_sizer2.Add(self.label_classif, 0, wx.ALIGN_LEFT | wx.ALIGN_CENTER_VERTICAL, 0) + grid_sizer2.Add(self.radio_box_classif, 0, wx.ALIGN_LEFT | wx.ALIGN_CENTER_VERTICAL, 0) + grid_sizer2.Add(wx.StaticLine(self), 0, wx.EXPAND | wx.ALL, 1) + grid_sizer2.Add(wx.StaticLine(self, -1), 0, wx.EXPAND | wx.ALL, 1) + + #grid_sizer2.Add(self.label_2, 0, wx.ALIGN_LEFT | wx.ALIGN_CENTER_VERTICAL, 0) + #grid_sizer2.Add(self.spin_ctrl_1, 0, wx.ALIGN_LEFT | wx.ALIGN_CENTER_VERTICAL, 0) + #grid_sizer2.Add(wx.StaticLine(self), 0, wx.EXPAND | wx.ALL, 1) + #grid_sizer2.Add(wx.StaticLine(self, -1), 0, wx.EXPAND | wx.ALL, 1) + + grid_sizer2.Add(self.label_max_actives, 0, wx.ALIGN_LEFT | wx.ALIGN_CENTER_VERTICAL, 0) + grid_sizer2.Add(self.spin_max_actives, 0, wx.ALIGN_LEFT | wx.ALIGN_CENTER_VERTICAL, 0) + grid_sizer2.Add(wx.StaticLine(self), 0, wx.EXPAND | wx.ALL, 1) + grid_sizer2.Add(wx.StaticLine(self, -1), 0, wx.EXPAND | wx.ALL, 1) + + grid_sizer2.Add(self.label_6, 0, wx.ALIGN_LEFT | wx.ALIGN_CENTER_VERTICAL, 0) + grid_sizer2.Add(self.spin_ctrl_3, 0, wx.ALIGN_LEFT | wx.ALIGN_CENTER_VERTICAL, 0) + grid_sizer2.Add(wx.StaticLine(self), 0, wx.EXPAND | wx.ALL, 1) + grid_sizer2.Add(wx.StaticLine(self, -1), 0, wx.EXPAND | wx.ALL, 1) + + grid_sizer2.Add(self.label_7, 0, wx.ALIGN_LEFT | wx.ALIGN_CENTER_VERTICAL, 0) + grid_sizer2.Add(self.spin_ctrl_4, 0, wx.ALIGN_LEFT | wx.ALIGN_CENTER_VERTICAL, 0) + grid_sizer2.Add(wx.StaticLine(self), 0, wx.EXPAND | wx.ALL, 1) + grid_sizer2.Add(wx.StaticLine(self, -1), 0, wx.EXPAND | wx.ALL, 1) + + grid_sizer2.Add(self.label_4, 0, wx.ALIGN_LEFT | wx.ALIGN_CENTER_VERTICAL, 0) + grid_sizer2.Add(self.button_5, 0, wx.ALIGN_LEFT | wx.ALIGN_CENTER_VERTICAL, 0) + grid_sizer2.Add(wx.StaticLine(self), 0, wx.EXPAND | wx.ALL, 1) + grid_sizer2.Add(wx.StaticLine(self, -1), 0, wx.EXPAND | wx.ALL, 1) + + grid_button.Add(self.button_1, 0, wx.ALIGN_CENTER_HORIZONTAL, 0) + grid_button.Add(self.button_2, 0, wx.ALIGN_CENTER_HORIZONTAL, 0) + grid_button.Add(self.button_4, 0, wx.ALIGN_CENTER_HORIZONTAL, 0) + sizer_2.Add(grid_sizer2, 3, wx.EXPAND, 0) + sizer_2.Add(grid_button, 0, wx.ALIGN_CENTER_HORIZONTAL | wx.ALIGN_CENTER_VERTICAL, 0) + sizer_1.Add(sizer_2, 0, wx.EXPAND, 0) + self.SetSizer(sizer_1) + sizer_1.Fit(self) + self.Layout() + + def OnKeyPref(self, event): + self.choose = True + dial = AlcOptFrame(self.parent, self) + dial.CenterOnParent() + val = dial.ShowModal() + + def OnDef(self, event): + ConfOri = ConstructConfigPath(self.parent.parent.AppliPath, user=False) + ConfUser = ConstructConfigPath(self.parent.parent.UserConfigPath) + shutil.copyfile(ConfOri['pam'], ConfUser['pam']) + self.parent.pamconf.read(self.parent.ConfigPath['pam']) + self.__set_properties() diff --git a/PrintRScript.py b/PrintRScript.py new file mode 100644 index 0000000..4d0fb40 --- /dev/null +++ b/PrintRScript.py @@ -0,0 +1,622 @@ +# -*- coding: utf-8 -*- +#Author: Pierre Ratinaud +#Copyright (c) 2008-2011 Pierre Ratinaud +#Lisense: GNU/GPL + +import tempfile +from chemins import ffr +import os +import locale +from datetime import datetime +import logging + +log = logging.getLogger('iramuteq.printRscript') + +class PrintRScript : + def __init__ (self, analyse): + log.info('Rscript') + self.pathout = analyse.pathout + self.analyse = analyse + self.scriptout = self.pathout['temp'] + self.script = u"#Script genere par IRaMuTeQ - %s" % datetime.now().ctime() + + def add(self, txt) : + self.script = '\n'.join([self.script, txt]) + + def defvar(self, name, value) : + self.add(' <- '.join([name, value])) + + def defvars(self, lvars) : + for val in lvars : + self.defvar(val[0],val[1]) + + def sources(self, lsources) : + for source in lsources : + self.add('source("%s")' % source) + + def load(self, l) : + for val in l : + self.add('load("%s")' % val) + + def write(self) : + with open(self.scriptout, 'w') as f : + f.write(self.script) + + +class chdtxt(PrintRScript) : + pass + + +class Alceste2(PrintRScript) : + def doscript(self) : + self.sources(['chdfunct']) + self.load(['Rdata']) + lvars = [['clnb', `self.analyse.clnb`], + ['Contout', '"%s"' % self.pathout['Contout']], + ['ContSupOut', '"%s"' % self.pathout['ContSupOut']], + ['ContEtOut', '"%s"' % self.pathout['ContEtOut']], + ['profileout', '"%s"' % self.pathout['profils.csv']], + ['antiout', '"%s"' % self.pathout['antiprofils.csv']], + ['chisqtable', '"%s"' % self.pathout['chisqtable.csv']], + ['ptable', '"%s"' % self.pathout['ptable.csv']]] + + self.defvars(lvars) + + + +# txt = "clnb<-%i\n" % clnb +# txt += """ +#source("%s") +#load("%s") +#""" % (RscriptsPath['chdfunct'], DictChdTxtOut['RData']) +# txt += """ +#dataact<-read.csv2("%s", header = FALSE, sep = ';',quote = '\"', row.names = 1, na.strings = 'NA') +#datasup<-read.csv2("%s", header = FALSE, sep = ';',quote = '\"', row.names = 1, na.strings = 'NA') +#dataet<-read.csv2("%s", header = FALSE, sep = ';',quote = '\"', row.names = 1, na.strings = 'NA') +#""" % (DictChdTxtOut['Contout'], DictChdTxtOut['ContSupOut'], DictChdTxtOut['ContEtOut']) +# txt += """ +#tablesqrpact<-BuildProf(as.matrix(dataact),n1,clnb) +#tablesqrpsup<-BuildProf(as.matrix(datasup),n1,clnb) +#tablesqrpet<-BuildProf(as.matrix(dataet),n1,clnb) +#""" +# txt += """ +#PrintProfile(n1,tablesqrpact[4],tablesqrpet[4],tablesqrpact[5],tablesqrpet[5],clnb,"%s","%s",tablesqrpsup[4],tablesqrpsup[5]) +#""" % (DictChdTxtOut['PROFILE_OUT'], DictChdTxtOut['ANTIPRO_OUT']) +# txt += """ +#colnames(tablesqrpact[[2]])<-paste('classe',1:clnb,sep=' ') +#colnames(tablesqrpact[[1]])<-paste('classe',1:clnb,sep=' ') +#colnames(tablesqrpsup[[2]])<-paste('classe',1:clnb,sep=' ') +#colnames(tablesqrpsup[[1]])<-paste('classe',1:clnb,sep=' ') +#colnames(tablesqrpet[[2]])<-paste('classe',1:clnb,sep=' ') +#colnames(tablesqrpet[[1]])<-paste('classe',1:clnb,sep=' ') +#chistabletot<-rbind(tablesqrpact[2][[1]],tablesqrpsup[2][[1]]) +#chistabletot<-rbind(chistabletot,tablesqrpet[2][[1]]) +#ptabletot<-rbind(tablesqrpact[1][[1]],tablesqrpet[1][[1]]) +#""" +# txt += """ +#write.csv2(chistabletot,file="%s") +#write.csv2(ptabletot,file="%s") +#gbcluster<-n1 +#write.csv2(gbcluster,file="%s") +#""" % (DictChdTxtOut['chisqtable'], DictChdTxtOut['ptable'], DictChdTxtOut['SbyClasseOut']) +# + + +def RchdTxt(DicoPath, RscriptPath, mincl, classif_mode, nbt = 9, libsvdc = False, libsvdc_path = None, R_max_mem = False): + txt = """ + source("%s") + source("%s") + source("%s") + source("%s") + """ % (RscriptPath['CHD'], RscriptPath['chdtxt'], RscriptPath['anacor'], RscriptPath['Rgraph']) + if R_max_mem : + txt += """ + memory.limit(%i) + """ % R_max_mem + + txt += """ + nbt <- %i + """ % nbt + if libsvdc : + txt += """ + libsvdc <- TRUE + libsvdc.path <- "%s" + """ % ffr(libsvdc_path) + else : + txt += """ + libsvdc <- FALSE + libsvdc.path <- NULL + """ + + txt +=""" + library(Matrix) + data1 <- readMM("%s") + data1 <- as(data1, "dgCMatrix") + row.names(data1) <- 1:nrow(data1) + """ % DicoPath['TableUc1'] + + if classif_mode == 0: + txt += """ + data2 <- readMM("%s") + data2 <- as(data2, "dgCMatrix") + row.names(data2) <- 1:nrow(data2) + """ % DicoPath['TableUc2'] + txt += """ + chd1<-CHD(data1, x = nbt, libsvdc = libsvdc, libsvdc.path = libsvdc.path) + """ + + if classif_mode == 0: + txt += """ + chd2<-CHD(data2, x = nbt, libsvdc = libsvdc, libsvdc.path = libsvdc.path) + """ + else: + txt += """ + chd2<-chd1 + """ + + txt += """ + #lecture des uce + listuce1<-read.csv2("%s") + """ % DicoPath['listeuce1'] + + if classif_mode == 0: + txt += """ + listuce2<-read.csv2("%s") + """ % DicoPath['listeuce2'] + + txt += """ +# rm(data1) + """ + + if classif_mode == 0: + txt += """ +# rm(data2) + """ + txt += """ + chd.result <- Rchdtxt("%s",mincl=%i,classif_mode=%i, nbt = nbt) + n1 <- chd.result$n1 + classeuce1 <- chd.result$cuce1 + classeuce2 <- chd.result$cuce2 + """ % (DicoPath['uce'], mincl, classif_mode) + + txt += """ + tree.tot1 <- make_tree_tot(chd1) +# open_file_graph("%s", widt = 600, height=400) +# plot(tree.tot1$tree.cl) +# dev.off() + """%DicoPath['arbre1'] + + if classif_mode == 0: + txt += """ + tree.tot2 <- make_tree_tot(chd2) +# open_file_graph("%s", width = 600, height=400) +# plot(tree.tot2$tree.cl) +# dev.off() + """ % DicoPath['arbre2'] + + txt += """ + tree.cut1 <- make_dendro_cut_tuple(tree.tot1$dendro_tuple, chd.result$coord_ok, classeuce1, 1, nbt) + save(tree.cut1, file="%s") + classes<-n1[,ncol(n1)] + open_file_graph("%s", width = 600, height=400) + plot.dendropr(tree.cut1$tree.cl,classes) + open_file_graph("%s", width = 600, height=400) + plot(tree.cut1$dendro_tot_cl) + dev.off() + """ % (DicoPath['Rdendro'], DicoPath['dendro1'], DicoPath['arbre1']) + + if classif_mode == 0: + txt += """ + tree.cut2 <- make_dendro_cut_tuple(tree.tot2$dendro_tuple, chd.result$coord_ok, classeuce2, 2, nbt) + open_file_graph("%s", width = 600, height=400) + plot(tree.cut2$tree.cl) + dev.off() + open_file_graph("%s", width = 600, height=400) + plot(tree.cut1$dendro_tot_cl) + dev.off() + """ % (DicoPath['dendro2'], DicoPath['arbre2']) + + txt += """ + save.image(file="%s") + """ % DicoPath['RData'] + fileout = open(DicoPath['Rchdtxt'], 'w') + fileout.write(txt) + fileout.close() + +def RPamTxt(corpus, RscriptPath): + DicoPath = corpus.dictpathout + param = corpus.parametre + print param + txt = """ + source("%s") + """ % (RscriptPath['pamtxt']) + txt += """ + source("%s") + """ % (RscriptPath['Rgraph']) + txt += """ + result <- pamtxt("%s", "%s", "%s", method = "%s", clust_type = "%s", clnb = %i) + n1 <- result$uce + """ % (DicoPath['TableUc1'], DicoPath['listeuce1'], DicoPath['uce'], param['method'], param['cluster_type'], param['nbcl'] ) + txt += """ + open_file_graph("%s", width=400, height=400) + plot(result$cl) + dev.off() + """ % (DicoPath['arbre1']) + txt += """ + save.image(file="%s") + """ % DicoPath['RData'] + fileout = open(DicoPath['Rchdtxt'], 'w') + fileout.write(txt) + fileout.close() + + +def RchdQuest(DicoPath, RscriptPath, nbcl = 10, mincl = 10): + txt = """ + source("%s") + source("%s") + source("%s") + source("%s") + """ % (RscriptPath['CHD'], RscriptPath['chdquest'], RscriptPath['anacor'],RscriptPath['Rgraph']) + + txt += """ + nbt <- %i - 1 + mincl <- %i + """ % (nbcl, mincl) + + txt += """ + chd.result<-Rchdquest("%s","%s","%s", nbt = nbt, mincl = mincl) + n1 <- chd.result$n1 + classeuce1 <- chd.result$cuce1 + """ % (DicoPath['Act01'], DicoPath['listeuce1'], DicoPath['uce']) + + txt += """ + tree_tot1 <- make_tree_tot(chd.result$chd) + open_file_graph("%s", width = 600, height=400) + plot(tree_tot1$tree.cl) + dev.off() + """%DicoPath['arbre1'] + + txt += """ + tree_cut1 <- make_dendro_cut_tuple(tree_tot1$dendro_tuple, chd.result$coord_ok, classeuce1, 1, nbt) + tree.cut1 <- tree_cut1 + save(tree.cut1, file="%s") + open_file_graph("%s", width = 600, height=400) + classes<-n1[,ncol(n1)] + plot.dendropr(tree_cut1$tree.cl,classes) + """ % (DicoPath['Rdendro'],DicoPath['dendro1']) + + txt += """ + save.image(file="%s") + """ % DicoPath['RData'] + fileout = open(DicoPath['Rchdquest'], 'w') + fileout.write(txt) + fileout.close() + +def AlcesteTxtProf(DictChdTxtOut, RscriptsPath, clnb, taillecar): + txt = "clnb<-%i\n" % clnb + txt += """ +source("%s") +load("%s") +""" % (RscriptsPath['chdfunct'], DictChdTxtOut['RData']) + txt += """ +dataact<-read.csv2("%s", header = FALSE, sep = ';',quote = '\"', row.names = 1, na.strings = 'NA') +datasup<-read.csv2("%s", header = FALSE, sep = ';',quote = '\"', row.names = 1, na.strings = 'NA') +dataet<-read.csv2("%s", header = FALSE, sep = ';',quote = '\"', row.names = 1, na.strings = 'NA') +""" % (DictChdTxtOut['Contout'], DictChdTxtOut['ContSupOut'], DictChdTxtOut['ContEtOut']) + txt += """ +tablesqrpact<-BuildProf(as.matrix(dataact),n1,clnb) +tablesqrpsup<-BuildProf(as.matrix(datasup),n1,clnb) +tablesqrpet<-BuildProf(as.matrix(dataet),n1,clnb) +""" + txt += """ +PrintProfile(n1,tablesqrpact[4],tablesqrpet[4],tablesqrpact[5],tablesqrpet[5],clnb,"%s","%s",tablesqrpsup[4],tablesqrpsup[5]) +""" % (DictChdTxtOut['PROFILE_OUT'], DictChdTxtOut['ANTIPRO_OUT']) + txt += """ +colnames(tablesqrpact[[2]])<-paste('classe',1:clnb,sep=' ') +colnames(tablesqrpact[[1]])<-paste('classe',1:clnb,sep=' ') +colnames(tablesqrpsup[[2]])<-paste('classe',1:clnb,sep=' ') +colnames(tablesqrpsup[[1]])<-paste('classe',1:clnb,sep=' ') +colnames(tablesqrpet[[2]])<-paste('classe',1:clnb,sep=' ') +colnames(tablesqrpet[[1]])<-paste('classe',1:clnb,sep=' ') +chistabletot<-rbind(tablesqrpact[2][[1]],tablesqrpsup[2][[1]]) +chistabletot<-rbind(chistabletot,tablesqrpet[2][[1]]) +ptabletot<-rbind(tablesqrpact[1][[1]],tablesqrpet[1][[1]]) +""" + txt += """ +write.csv2(chistabletot,file="%s") +write.csv2(ptabletot,file="%s") +gbcluster<-n1 +write.csv2(gbcluster,file="%s") +""" % (DictChdTxtOut['chisqtable'], DictChdTxtOut['ptable'], DictChdTxtOut['SbyClasseOut']) + if clnb > 2 : + txt += """ + library(ca) + colnames(dataact)<-paste('classe',1:clnb,sep=' ') + colnames(datasup)<-paste('classe',1:clnb,sep=' ') + colnames(dataet)<-paste('classe',1:clnb,sep=' ') + rowtot<-nrow(dataact)+nrow(dataet)+nrow(datasup) + afctable<-rbind(as.matrix(dataact),as.matrix(datasup)) + afctable<-rbind(afctable,as.matrix(dataet)) + colnames(afctable)<-paste('classe',1:clnb,sep=' ') + afc<-ca(afctable,suprow=((nrow(dataact)+1):rowtot),nd=(ncol(afctable)-1)) + debsup<-nrow(dataact)+1 + debet<-nrow(dataact)+nrow(datasup)+1 + fin<-rowtot + afc<-AddCorrelationOk(afc) + """ + #FIXME : split this!!! + txt += """ + source("%s") + """ % RscriptsPath['Rgraph'] + + txt += """ + afc <- summary.ca.dm(afc) + afc_table <- create_afc_table(afc) + write.csv2(afc_table$facteur, file = "%s") + write.csv2(afc_table$colonne, file = "%s") + write.csv2(afc_table$ligne, file = "%s") + """ % (DictChdTxtOut['afc_facteur'], DictChdTxtOut['afc_col'], DictChdTxtOut['afc_row']) + + txt += """ + xlab <- paste('facteur 1 - ', round(afc$facteur[1,2],2), sep = '') + ylab <- paste('facteur 2 - ', round(afc$facteur[2,2],2), sep = '') + xlab <- paste(xlab, ' %', sep = '') + ylab <- paste(ylab, ' %', sep = '') + """ + + txt += """ + PARCEX<-%s + """ % taillecar + txt += """ + PlotAfc2dCoul(afc, as.data.frame(chistabletot), "%s", what='coord', deb=1, fin=(debsup-1), xlab = xlab, ylab = ylab) + """ % (DictChdTxtOut['AFC2DL_OUT']) + txt += """ + PlotAfc2dCoul(afc, as.data.frame(chistabletot), "%s", what='coord', deb=debsup, fin=(debet-1), xlab = xlab, ylab = ylab) + """ % (DictChdTxtOut['AFC2DSL_OUT']) + txt += """ + PlotAfc2dCoul(afc, as.data.frame(chistabletot), "%s", what='coord', deb=debet, fin=fin, xlab = xlab, ylab = ylab) + """ % (DictChdTxtOut['AFC2DEL_OUT']) + txt += """ + PlotAfc2dCoul(afc, as.data.frame(chistabletot), "%s", col=TRUE, what='coord', xlab = xlab, ylab = ylab) + """ % (DictChdTxtOut['AFC2DCL_OUT']) + txt += """ + PlotAfc2dCoul(afc, as.data.frame(chistabletot), "%s", what='crl', deb=1, fin=(debsup-1), xlab = xlab, ylab = ylab) + PlotAfc2dCoul(afc, as.data.frame(chistabletot), "%s", what='crl', deb=debsup, fin=(debet-1), xlab = xlab, ylab = ylab) + PlotAfc2dCoul(afc, as.data.frame(chistabletot), "%s", what='crl', deb=debet, fin=fin, xlab = xlab, ylab = ylab) + PlotAfc2dCoul(afc, as.data.frame(chistabletot), "%s", col=TRUE, what='crl', xlab = xlab, ylab = ylab) + """ % (DictChdTxtOut['AFC2DCoul'], DictChdTxtOut['AFC2DCoulSup'], DictChdTxtOut['AFC2DCoulEt'], DictChdTxtOut['AFC2DCoulCl']) + + txt += """ +#rm(dataact) +#rm(datasup) +#rm(dataet) +rm(tablesqrpact) +rm(tablesqrpsup) +rm(tablesqrpet) +save.image(file="%s") +""" % DictChdTxtOut['RData'] + file = open(DictChdTxtOut['RTxtProfGraph'], 'w') + file.write(txt) + file.close() + + +def write_afc_graph(self): + if self.param['over'] : over = 'TRUE' + else : over = 'FALSE' + + if self.param['do_select_nb'] : do_select_nb = 'TRUE' + else : do_select_nb = 'FALSE' + + if self.param['do_select_chi'] : do_select_chi = 'TRUE' + else : do_select_chi = 'FALSE' + + if self.param['cex_txt'] : cex_txt = 'TRUE' + else : cex_txt = 'FALSE' + + if self.param['tchi'] : tchi = 'TRUE' + else : tchi = 'FALSE' + + with open(self.RscriptsPath['afc_graph'], 'r') as f: + txt = f.read() + +# self.DictPathOut['RData'], \ + scripts = txt % (self.RscriptsPath['Rgraph'],\ + self.param['typegraph'], \ + self.param['what'], \ + self.param['facteur'][0],\ + self.param['facteur'][1], \ + self.param['facteur'][2], \ + self.param['qui'], \ + over, do_select_nb, \ + self.param['select_nb'], \ + do_select_chi, \ + self.param['select_chi'], \ + cex_txt, \ + self.param['txt_min'], \ + self.param['txt_max'], \ + self.fileout, \ + self.param['width'], \ + self.param['height'],\ + self.param['taillecar'], \ + self.param['alpha'], \ + self.param['film'], \ + tchi,\ + self.param['tchi_min'],\ + self.param['tchi_max'],\ + ffr(os.path.dirname(self.fileout))) + return scripts + +def print_simi3d(self): + simi3d = self.parent.simi3dpanel + txt = '#Fichier genere par Iramuteq' + if simi3d.movie.GetValue() : + movie = "'" + ffr(os.path.dirname(self.DictPathOut['RData'])) + "'" + else : + movie = 'NULL' + if self.section == 'chd_dist_quest' : + header = 'TRUE' + else : + header = 'FALSE' + txt += """ + dm<-read.csv2("%s",row.names=1,header = %s) + load("%s") + """ % (self.DictPathOut['Contout'], header, self.DictPathOut['RData']) + + txt += """ + source("%s") + """ % self.parent.RscriptsPath['Rgraph'] + + + txt += """ + make.simi.afc(dm,chistabletot, lim=%i, alpha = %.2f, movie = %s) + """ % (simi3d.spin_1.GetValue(), float(simi3d.slider_1.GetValue())/100, movie) + tmpfile = tempfile.mktemp(dir=self.parent.TEMPDIR) + tmp = open(tmpfile,'w') + tmp.write(txt) + tmp.close() + return tmpfile + +def dendroandbarplot(table, rownames, colnames, rgraph, tmpgraph, intxt = False, dendro=False) : + if not intxt : + txttable = 'c(' + ','.join([','.join(line) for line in table]) + ')' + rownb = len(rownames) + rownames = 'c("' + '","'.join(rownames) + '")' + colnames = 'c("' + '","'.join(colnames) + '")' + if not intxt : + #FIXME + txt = """ + di <- matrix(data=%s, nrow=%i, byrow = TRUE) + rownames(di)<- %s + colnames(di) <- %s + """ % (txttable, rownb, rownames, colnames) + else : + txt = intxt + txt += """ + load("%s") + library(ape) + source("%s") + height <- (30*ncol(di)) + (15*nrow(di)) + height <- ifelse(height <= 400, 400, height) + width <- 500 + open_file_graph("%s", width=width, height=height) + plot.dendro.lex(tree.cut1$tree.cl, di) + """ % (ffr(dendro),ffr(rgraph), ffr(tmpgraph)) + return txt + +def barplot(table, rownames, colnames, rgraph, tmpgraph, intxt = False) : + if not intxt : + txttable = 'c(' + ','.join([','.join(line) for line in table]) + ')' + #width = 100 + (15 * len(rownames)) + (100 * len(colnames)) + #height = len(rownames) * 15 + rownb = len(rownames) + #if height < 400 : + # height = 400 + rownames = 'c("' + '","'.join(rownames) + '")' + colnames = 'c("' + '","'.join(colnames) + '")' + if not intxt : + #FIXME + txt = """ + inf <- NA + di <- matrix(data=%s, nrow=%i, byrow = TRUE) + di[is.na(di)] <- max(di, na.rm=TRUE) + 2 + rownames(di)<- %s + colnames(di) <- %s + """ % (txttable, rownb, rownames, colnames) + else : + txt = intxt + txt += """ + source("%s") + color = rainbow(nrow(di)) + width <- 100 + (20*length(rownames(di))) + (100 * length(colnames(di))) + height <- nrow(di) * 15 + if (height < 400) { height <- 400} + open_file_graph("%s",width = width, height = height) + par(mar=c(0,0,0,0)) + layout(matrix(c(1,2),1,2, byrow=TRUE),widths=c(3,lcm(7))) + par(mar=c(2,2,1,0)) + coord <- barplot(as.matrix(di), beside = TRUE, col = color, space = c(0.1,0.6)) + c <- colMeans(coord) + c1 <- c[-1] + c2 <- c[-length(c)] + cc <- cbind(c1,c2) + lcoord <- apply(cc, 1, mean) + abline(v=lcoord) + if (min(di) < 0) { + amp <- abs(max(di) - min(di)) + } else { + amp <- max(di) + } + if (amp < 10) { + d <- 2 + } else { + d <- signif(amp%%/%%10,1) + } + mn <- round(min(di)) + mx <- round(max(di)) + for (i in mn:mx) { + if ((i/d) == (i%%/%%d)) { + abline(h=i,lty=3) + } + } + par(mar=c(0,0,0,0)) + plot(0, axes = FALSE, pch = '') + legend(x = 'center' , rownames(di), fill = color) + dev.off() + """ % (rgraph, ffr(tmpgraph)) + return txt + +#def RAfcUci(DictAfcUciOut, nd=2, RscriptsPath='', PARCEX='0.8'): +# txt = """ +# library(ca) +# nd<-%i +# """ % nd +# txt += """ +# dataact<-read.csv2("%s") +# """ % (DictAfcUciOut['TableCont'])#, encoding) +# txt += """ +# datasup<-read.csv2("%s") +# """ % (DictAfcUciOut['TableSup'])#, encoding) +# txt += """ +# dataet<-read.csv2("%s") +# """ % (DictAfcUciOut['TableEt'])#, encoding) +# txt += """ +# datatotsup<-cbind(dataact,datasup) +# datatotet<-cbind(dataact,dataet) +# afcact<-ca(dataact,nd=nd) +# afcsup<-ca(datatotsup,supcol=((ncol(dataact)+1):ncol(datatotsup)),nd=nd) +# afcet<-ca(datatotet,supcol=((ncol(dataact)+1):ncol(datatotet)),nd=nd) +# afctot<-afcsup$colcoord +# rownames(afctot)<-afcsup$colnames +# colnames(afctot)<-paste('coord. facteur',1:nd,sep=' ') +# afctot<-cbind(afctot,mass=afcsup$colmass) +# afctot<-cbind(afctot,distance=afcsup$coldist) +# afctot<-cbind(afctot,intertie=afcsup$colinertia) +# rcolet<-afcet$colsup +# afctmp<-afcet$colcoord[rcolet,] +# rownames(afctmp)<-afcet$colnames[rcolet] +# afctmp<-cbind(afctmp,afcet$colmass[rcolet]) +# afctmp<-cbind(afctmp,afcet$coldist[rcolet]) +# afctmp<-cbind(afctmp,afcet$colinertia[rcolet]) +# afctot<-rbind(afctot,afctmp) +# write.csv2(afctot,file = "%s") +# source("%s") +# """ % (DictAfcUciOut['afc_row'], RscriptsPath['Rgraph']) +# txt += """ +# PARCEX=%s +# """ % PARCEX +# #FIXME +# txt += """ +# PlotAfc(afcet,filename="%s",toplot=c%s, PARCEX=PARCEX) +# """ % (DictAfcUciOut['AfcColAct'], "('none','active')") +# txt += """ +# PlotAfc(afcsup,filename="%s",toplot=c%s, PARCEX=PARCEX) +# """ % (DictAfcUciOut['AfcColSup'], "('none','passive')") +# txt += """PlotAfc(afcet,filename="%s", toplot=c%s, PARCEX=PARCEX) +# """ % (DictAfcUciOut['AfcColEt'], "('none','passive')") +# txt += """ +# PlotAfc(afcet,filename="%s", toplot=c%s, PARCEX=PARCEX) +# """ % (DictAfcUciOut['AfcRow'], "('all','none')") +# f = open(DictAfcUciOut['Rafcuci'], 'w') +# f.write(txt) +# f.close() + diff --git a/ProfList.py b/ProfList.py new file mode 100644 index 0000000..db1dbd1 --- /dev/null +++ b/ProfList.py @@ -0,0 +1,875 @@ +# -*- coding: utf-8 -*- + +#---------------------------------------------------------------------------- +# Name: ListCtrl.py +# Author: Pierre Ratinaud +# + +#comes from ListCtrl.py from the demo tool of wxPython: +# Author: Robin Dunn & Gary Dumer +# +# Created: +# Copyright: (c) 1998 by Total Control Software +# Licence: wxWindows license +#---------------------------------------------------------------------------- + +import os +import sys +import wx +import wx.lib.mixins.listctrl as listmix +from tabsimi import DoSimi +from listlex import ListForSpec +from chemins import ConstructPathOut, ffr +from dialog import PrefExport, PrefUCECarac, SearchDial +from tableau import Tableau +from search_tools import SearchFrame +import webbrowser +import cStringIO +import tempfile +import codecs +from functions import exec_rcode, MessageImage, progressbar, treat_var_mod +from PrintRScript import barplot +from textclassechd import ClasseCHD +from shutil import copyfile + +#--------------------------------------------------------------------------- + +class ProfListctrl(wx.ListCtrl, listmix.ListCtrlAutoWidthMixin): + def __init__(self, parent, ID, pos=wx.DefaultPosition, + size=wx.DefaultSize, style=0): + wx.ListCtrl.__init__(self, parent, ID, pos, size, style) + listmix.ListCtrlAutoWidthMixin.__init__(self) + + +class ProfListctrlPanel(wx.Panel, listmix.ColumnSorterMixin): + def __init__(self, parent, gparent, ProfClasse, Alceste=False, cl=0): + self.parent = parent + classe = ProfClasse + self.cl = cl + self.Source = gparent + if 'tableau' in dir(self.Source): + self.tableau = self.Source.tableau + self.Alceste = Alceste + self.var_mod = {} + + + wx.Panel.__init__(self, parent, -1, style=wx.WANTS_CHARS) + + search_id = wx.NewId() + searchall_id = wx.NewId() + self.parent.Bind(wx.EVT_MENU, self.onsearch, id = search_id) + self.parent.Bind(wx.EVT_MENU, self.onsearchall, id = searchall_id) + self.accel_tbl = wx.AcceleratorTable([(wx.ACCEL_CTRL, ord('F'), search_id), + (wx.ACCEL_CTRL|wx.ACCEL_SHIFT, ord('F'), searchall_id)]) + self.SetAcceleratorTable(self.accel_tbl) + + self.il = wx.ImageList(16, 16) +# self.idx1 = self.il.Add(images.getSmilesBitmap()) + self.sm_up = self.il.Add(getSmallUpArrowBitmap()) + self.sm_dn = self.il.Add(getSmallDnArrowBitmap()) + tID = wx.NewId() + + self.list = ProfListctrl(self, tID, + style=wx.LC_REPORT + | wx.BORDER_NONE + | wx.LC_EDIT_LABELS + | wx.LC_SORT_ASCENDING + ) + line1 = classe[0] + limit = 0 + limitsup = 0 + i = 0 + dictdata = {} + limit = [i for i,b in enumerate(classe[1:]) if b[0] == '*'] + if limit != [] : + limit = limit[0] - 1 + limitsup = [i for i,b in enumerate(classe[1:]) if b[0] == '*****'] + if limitsup == [] : + limitsup = 0 + else : + limitsup = limitsup[0] + classen = [line for line in classe[1:] if line[0] != '*' and line[0] != '*****'] + if limit == [] : + limit = len(classen) - 1 + dictdata = dict(zip([i for i in range(0,len(classen))], classen)) + #if not self.Alceste : + # limit = limit + 1 + self.list.SetImageList(self.il, wx.IMAGE_LIST_SMALL) + + self.PopulateList(dictdata, limit, limitsup, Alceste) + + self.Bind(wx.EVT_SIZE, self.OnSize) + self.Bind(wx.EVT_LIST_COL_CLICK, self.OnColClick) + self.Bind(wx.EVT_LIST_ITEM_SELECTED, self.OnItemSelected, self.list) + + # for wxMSW + self.list.Bind(wx.EVT_COMMAND_RIGHT_CLICK, self.OnRightClick) + + # for wxGTK + self.list.Bind(wx.EVT_RIGHT_UP, self.OnRightClick) + self.itemDataMap = dictdata + listmix.ColumnSorterMixin.__init__(self, 8) + self.do_greyline() +#----------------------------------------------------------------------------------------- + + def PopulateList(self, dictdata, limit, limitsup, Alceste): + + + # for normal, simple columns, you can add them like this: + self.list.InsertColumn(0, "num", wx.LIST_FORMAT_RIGHT) + self.list.InsertColumn(1, "eff. uce", wx.LIST_FORMAT_RIGHT) + self.list.InsertColumn(2, "eff. total", wx.LIST_FORMAT_RIGHT) + self.list.InsertColumn(3, "pourcentage", wx.LIST_FORMAT_RIGHT) + self.list.InsertColumn(4, "chi2", wx.LIST_FORMAT_RIGHT) + self.list.InsertColumn(5, "Type", wx.LIST_FORMAT_RIGHT) + self.list.InsertColumn(6, "forme", wx.LIST_FORMAT_RIGHT) + self.list.InsertColumn(7, "p", wx.LIST_FORMAT_RIGHT) + + for key in dictdata : #.iteritems(): + index = self.list.InsertStringItem(sys.maxint, '%4i' % key) + i = 1 + for val in dictdata[key][1:]: + self.list.SetStringItem(index, i, str(dictdata[key][i])) + i += 1 + self.list.SetItemData(index, key) + + self.list.SetColumnWidth(0, 60) + self.list.SetColumnWidth(1, 70) + self.list.SetColumnWidth(2, 80) + self.list.SetColumnWidth(3, 100) + self.list.SetColumnWidth(4, 70) + self.list.SetColumnWidth(5, wx.LIST_AUTOSIZE) + self.list.SetColumnWidth(6, wx.LIST_AUTOSIZE) + self.list.SetColumnWidth(7, wx.LIST_AUTOSIZE) + + # show how to change the colour of a couple items + if limitsup != 0 : + for i in range(limitsup, limit): + item = self.list.GetItem(i) + item.SetTextColour(wx.RED) + self.list.SetItem(item) + else : + limit=limit+1 + + for i in range(limit, len(dictdata)): + item = self.list.GetItem(i) + item.SetTextColour(wx.BLUE) + self.list.SetItem(item) + + if limitsup != 0 : + self.la = [self.getColumnText(i,6) for i in range(0, limitsup-1)] + self.lchi = [float(self.getColumnText(i,4)) for i in range(0, limitsup-1)] + self.lfreq = [int(self.getColumnText(i,1)) for i in range(0, limitsup-1)] + else : + self.la = [self.getColumnText(i,6) for i in range(0, limit)] + self.lfreq = [int(self.getColumnText(i,1)) for i in range(0, limit)] + self.lchi = [float(self.getColumnText(i,4)) for i in range(0, limit)] + + def do_greyline(self): + for row in xrange(self.list.GetItemCount()): + if row % 2 : + self.list.SetItemBackgroundColour(row, (230, 230, 230)) + else : + self.list.SetItemBackgroundColour(row, wx.WHITE) + + + # Used by the ColumnSorterMixin, see wx/lib/mixins/listctrl.py + def GetListCtrl(self): + return self.list + + # Used by the ColumnSorterMixin, see wx/lib/mixins/listctrl.py + def GetSortImages(self): + return (self.sm_dn, self.sm_up) + + + def OnRightDown(self, event): + x = event.GetX() + y = event.GetY() + item, flags = self.list.HitTest((x, y)) + + if flags & wx.LIST_HITTEST_ONITEM: + self.list.Select(item) + + event.Skip() + + + def getColumnText(self, index, col): + item = self.list.GetItem(index, col) + return item.GetText() + + + def OnItemSelected(self, event): + self.currentItem = event.m_itemIndex + event.Skip() + + def onsearch(self, evt) : + self.dial = SearchDial(self, self, 6, True) + self.dial.CenterOnParent() + self.dial.ShowModal() + self.dial.Destroy() + + def onsearchall(self, evt) : + if 'FrameSearch' not in dir(self.Source) : + self.Source.FrameSearch = SearchFrame(self.parent, -1, u"Rechercher...", self.Source.corpus) + self.dial = SearchDial(self, self.Source.FrameSearch.liste, 1, False) + self.dial.CenterOnParent() + self.dial.ShowModal() + self.dial.Destroy() + + def OnRightClick(self, event): + + # only do this part the first time so the events are only bound once + if self.Alceste: + if not hasattr(self, "popupID1"): + self.popupID1 = wx.NewId() + self.popupID2 = wx.NewId() + self.popupID3 = wx.NewId() + self.popupID4 = wx.NewId() + self.popupID5 = wx.NewId() + self.popupID6 = wx.NewId() + self.popupID7 = wx.NewId() + self.popupID8 = wx.NewId() + self.popupID9 = wx.NewId() + #self.popupID10 = wx.NewId() + self.popupIDgraph = wx.NewId() + self.idseg = wx.NewId() + self.iducecarac = wx.NewId() + self.idtablex = wx.NewId() + self.idchimod = wx.NewId() + self.idwordgraph = wx.NewId() + self.popup_proxe = wx.NewId() + self.idlexdendro = wx.NewId() + self.idexport = wx.NewId() + # self.export_classes = wx.NewId() + + self.Bind(wx.EVT_MENU, self.OnPopupOne, id=self.popupID1) + self.Bind(wx.EVT_MENU, self.OnPopupTwo, id=self.popupID2) + self.Bind(wx.EVT_MENU, self.OnPopupThree, id=self.popupID3) + self.Bind(wx.EVT_MENU, self.OnPopupFour, id=self.popupID4) + self.Bind(wx.EVT_MENU, self.OnPopupFive, id=self.popupID5) + self.Bind(wx.EVT_MENU, self.OnPopupSix, id=self.popupID6) + self.Bind(wx.EVT_MENU, self.OnPopupSeven, id=self.popupID7) + self.Bind(wx.EVT_MENU, self.OnPopupHeight, id=self.popupID8) + self.Bind(wx.EVT_MENU, self.OnPopupNine, id=self.popupID9) + #self.Bind(wx.EVT_MENU, self.OnPopupSpec, id=self.popupID10) + self.Bind(wx.EVT_MENU, self.on_graph, id=self.popupIDgraph) + self.Bind(wx.EVT_MENU, self.on_segments, id=self.idseg) + self.Bind(wx.EVT_MENU, self.on_uce_carac, id = self.iducecarac) + self.Bind(wx.EVT_MENU, self.on_tablex, id = self.idtablex) + self.Bind(wx.EVT_MENU, self.quest_var_mod, id = self.idchimod) + self.Bind(wx.EVT_MENU, self.onwordgraph, id = self.idwordgraph) + self.Bind(wx.EVT_MENU, self.onproxe, id = self.popup_proxe) + self.Bind(wx.EVT_MENU, self.onlexdendro, id = self.idlexdendro) + self.Bind(wx.EVT_MENU, self.onexport, id = self.idexport) + # self.Bind(wx.EVT_MENU, self.on_export_classes, id = self.export_classes) + # self.Bind(wx.EVT_MENU, self.OnPopupThree, id=self.popupID3) + + # make a menu + menu = wx.Menu() + menu.Append(self.popupID1, u"Formes associées") + menu.Append(self.idtablex, u"Chi2 par classe") + menu.Append(self.idlexdendro, u"Chi2 par classe + dendro") + menu.Append(self.idchimod, u"Chi2 modalités de la variable") + menu.Append(self.idwordgraph, u"Graphe du mot") + #menu.Append(self.export_classes, u"Exporter le corpus...") + + #menu.Append(self.popupID10, u"Spécificités") + + menu_conc = wx.Menu() + menu_conc.Append(self.popupID2, u"dans les uce de la classe") + menu_conc.Append(self.popupID3, u"dans les uce classées") + menu_conc.Append(self.popupID4, u"dans toutes les uce") + menu.AppendMenu(-1, u"Concordancier", menu_conc) + menu_cnrtl = wx.Menu() + menu_cnrtl.Append(self.popupID5, u"Définition") + menu_cnrtl.Append(self.popupID6, u"Etymologie") + menu_cnrtl.Append(self.popupID7, u"Synonymie") + menu_cnrtl.Append(self.popupID8, u"Antonymie") + menu_cnrtl.Append(self.popupID9, u"Morphologie") + menu_cnrtl.Append(self.popup_proxe, u"Proxémie") + menu.AppendMenu(-1, u"Outils du CNRTL", menu_cnrtl) + menu.AppendSeparator() + menu.Append(self.popupIDgraph, u"Graphe de la classe") + menu.Append(self.idseg, u"Segments répétés") + menu.Append(self.iducecarac, u"UCE caractéristiques") + menu.Append(self.idexport, 'Partitionner...') + #menu.Append(self.popupID2, u"Concordancier") + # menu.Append(self.popupID3, "recharger") + + self.PopupMenu(menu) + menu.Destroy() + elif 'tableau' in dir(self.Source) : + if not hasattr(self, "pop1"): + self.pop1 = wx.NewId() + self.pop2 = wx.NewId() + self.pop3 = wx.NewId() + self.Bind(wx.EVT_MENU, self.quest_simi, id=self.pop1) + self.Bind(wx.EVT_MENU, self.on_tablex, id=self.pop2) + self.Bind(wx.EVT_MENU, self.quest_var_mod, id=self.pop3) + + menu = wx.Menu() + menu.Append(self.pop2, u"Chi2 par classe") + menu.Append(self.pop3, u"Chi2 modalités de la variable") + menu.AppendSeparator() + menu.Append(self.pop1, u"Graph de la classe") + self.PopupMenu(menu) + menu.Destroy() + + def onexport(self, evt) : + if 'corpus' in dir(self.Source): + corpus = self.Source.corpus + ClasseCHD(self.parent, corpus, self.cl) + + def quest_var_mod(self, evt) : + if 'corpus' in dir(self.Source): + corpus = self.Source.corpus + if self.var_mod == {} : + self.var_mod = treat_var_mod([val for val in corpus.make_etoiles()]) + else : + corpus = self.Source.tableau + if self.var_mod == {} : + self.var_mod = treat_var_mod([val for val in corpus.actives] + [val for val in corpus.sups]) + with codecs.open(self.Source.pathout['chisqtable'], 'r', corpus.parametres['syscoding']) as f : + chistable = [line.replace('\n','').replace('\r','').replace('"','').replace(',','.').split(';') for line in f] + title = chistable[0] + title.pop(0) + chistable.pop(0) + vchistable = [line[1:] for line in chistable] + fchistable = [line[0] for line in chistable] + word = self.getColumnText(self.list.GetFirstSelected(), 6) + if len(word.split('_')) > 1 : + var = word.split('_')[0] + words = [word for word in self.var_mod[var]] + words.sort() + tableout = [] + kwords = [] + for word in words : + if word in fchistable : + tableout.append(vchistable[fchistable.index(word)]) + kwords.append(word) + tmpgraph = tempfile.mktemp(dir=self.Source.parent.TEMPDIR) + txt = barplot(tableout, kwords, title, self.Source.parent.RscriptsPath['Rgraph'], tmpgraph) + tmpscript = tempfile.mktemp(dir=self.Source.parent.TEMPDIR) + file = open(tmpscript,'w') + file.write(txt) + file.close() + exec_rcode(self.Source.parent.RPath, tmpscript, wait = True) + win = MessageImage(self, -1, u"Graphique", size=(700, 500),style = wx.DEFAULT_FRAME_STYLE) + win.addsaveimage(tmpgraph) + txt = "" % tmpgraph + win.HtmlPage.SetPage(txt) + win.Show(True) + else : + dial = wx.MessageDialog(self, u"Ce n'est pas une forme du type variable_modalité", u"Problème", wx.OK | wx.ICON_WARNING) + dial.CenterOnParent() + dial.ShowModal() + dial.Destroy() + + def quest_simi(self, evt) : + tableau = self.Source.tableau + tab = tableau.make_table_from_classe(self.cl, self.la) + pathout = ConstructPathOut(self.Source.pathout+'/', 'simi_classe_%i' %self.cl) + self.filename = os.path.join(pathout,'mat01.csv') + tableau.printtable(self.filename, tab) + del tab + paramsimi = {'coeff' : 0, + 'layout' : 2, + 'type' : 1, + 'arbremax' : 1, + 'coeff_tv' : 1, + 'coeff_tv_nb' : 0, + 'tvprop' : 0, + 'tvmin' : 5, + 'tvmax' : 30, + 'coeff_te' : 1, + 'coeff_temin' : 1, + 'coeff_temax' : 10, + 'label_v': 1, + 'label_e': 1, + 'vcex' : 0, + 'vcexmin' : 10, + 'vcexmax' : 25, + 'cex' : 10, + 'cexfromchi' : True, + 'sfromchi': False, + 'seuil_ok' : 0, + 'seuil' : 1, + 'cols' : (255,0,0), + 'cola' : (200,200,200), + 'width' : 1000, + 'height' : 1000, + 'first' : True, + 'keep_coord' : True, + 'alpha' : 20, + 'film': False, + } +# self.tableau.actives = {} +# self.tableau.lchi = self.lchi +# self.tableau.chi = {} +# for i, val in enumerate(self.la) : +# self.tableau.actives[val] = [self.lfreq[i]] +# self.tableau.chi[val] = [self.lchi[i]] + + act = {} + self.tableau.chi = {} + self.tableau.lchi = self.lchi + self.tableau.parametre['fromprof'] = True + for i, val in enumerate(self.la) : + act[val] = [self.lfreq[i]] + self.tableau.chi[val] = [self.lchi[i]] + DoSimi(self, param = paramsimi, fromprof = ffr(self.filename), pathout = pathout, listactives = self.la, actives = act) + + def onwordgraph(self, evt): + word = self.getColumnText(self.list.GetFirstSelected(), 6) + dlg = progressbar(self, 2) + corpus = self.Source.corpus + uces = corpus.lc[self.cl-1] + dlg.Update(1, u'Tableau...') + #tab = corpus.make_table_with_classe(uces, self.la) + pathout = ConstructPathOut(self.Source.pathout.dirout + '/' , 'simi_%s' % word) + self.filename = os.path.join(pathout,'mat01.csv') + dlg.Update(2, u'Ecriture...') + #corpus.write_tab(tab, self.filename) + #del tab + corpus.make_and_write_sparse_matrix_from_classe(self.la, uces, self.filename) + dlg.Destroy() + paramsimi = {'coeff' : 0, + 'layout' : 2, + 'type' : 1, + 'arbremax' : 0, + 'coeff_tv' : 1, + 'coeff_tv_nb' : 0, + 'tvprop' : 0, + 'tvmin' : 5, + 'tvmax' : 30, + 'coeff_te' : 1, + 'coeff_temin' : 1, + 'coeff_temax' : 10, + 'label_v': 1, + 'label_e': 0, + 'vcex' : 1, + 'vcexmin' : 10, + 'vcexmax' : 25, + 'cex' : 10, + 'seuil_ok' : 1, + 'seuil' : 1, + 'cols' : (255,0,0), + 'cola' : (200,200,200), + 'width' : 600, + 'height' : 600, + 'first' : True, + 'keep_coord' : True, + 'alpha' : 20, + 'film': False, + } + self.tableau = Tableau(self.parent, '') + self.tableau.listactives = self.la + self.tableau.actives = {} + for i, val in enumerate(self.la) : + self.tableau.actives[val] = [self.lfreq[i]] + DoSimi(self, param = paramsimi, fromprof = ffr(self.filename), pathout = pathout, wordgraph = word) + + + def OnPopupOne(self, event): + corpus = self.Source.corpus + #print 'ATTENTION PRINT ET TABLE' + #corpus.make_et_table() + word = self.getColumnText(self.list.GetFirstSelected(), 6) + lems = corpus.getlems() + uces = corpus.lc[self.cl-1] + rep = [] + #FIXME : donner aussi eff reel a la place de nb uce + for forme in lems[word].formes : + ucef = list(set(corpus.getworduces(forme)).intersection(uces)) + #ucef = [uce for uce in corpus.formes[forme][1] if uce in uces] + if ucef != [] : + nb = len(ucef) + rep.append([corpus.getforme(forme).forme, nb]) + win = message(self, -1, u"Formes associées", size=(300, 200), style=wx.DEFAULT_FRAME_STYLE) + win.html = '\n' + '
    '.join([' : '.join([str(val) for val in forme]) for forme in rep]) + '\n' + win.HtmlPage.SetPage(win.html) + win.Show(True) + + def on_graph(self, evt): + dlg = progressbar(self, 2) + corpus = self.Source.corpus + uces = corpus.lc[self.cl-1] + dlg.Update(1, u'Tableau...') + #tab = corpus.make_table_with_classe(uces, self.la) + pathout = ConstructPathOut(self.Source.pathout.dirout+'/', 'simi_classe_%i' %self.cl) + self.filename = os.path.join(pathout,'mat01.csv') + dlg.Update(2, u'Ecriture...') + #corpus.write_tab(tab, self.filename) + #del tab + corpus.make_and_write_sparse_matrix_from_classe(self.la, uces, self.filename) + dlg.Destroy() + paramsimi = {'coeff' : 0, + 'layout' : 2, + 'type' : 1, + 'arbremax' : 1, + 'coeff_tv' : 1, + 'coeff_tv_nb' : 0, + 'tvprop' : 0, + 'tvmin' : 5, + 'tvmax' : 30, + 'coeff_te' : 1, + 'coeff_temin' : 1, + 'coeff_temax' : 10, + 'label_v': 1, + 'label_e': 0, + 'vcex' : 0, + 'vcexmin' : 10, + 'vcexmax' : 25, + 'cex' : 10, + 'cexfromchi' : True, + 'sfromchi': False, + 'seuil_ok' : 0, + 'seuil' : 1, + 'cols' : (255,0,0), + 'cola' : (200,200,200), + 'width' : 1000, + 'height' : 1000, + 'first' : True, + 'keep_coord' : True, + 'alpha' : 20, + 'film': False, + } + self.tableau = Tableau(self.parent, '') + self.tableau.listactives = self.la + self.tableau.actives = {} + self.tableau.lchi = self.lchi + self.tableau.chi = {} + self.tableau.parametre['fromprof'] = True + for i, val in enumerate(self.la) : + self.tableau.actives[val] = [self.lfreq[i]] + self.tableau.chi[val] = [self.lchi[i]] + DoSimi(self, param = paramsimi, fromprof = ffr(self.filename), pathout = pathout) + + def on_segments(self,evt) : + dlg = progressbar(self, 2) + corpus = self.Source.corpus + uces = corpus.lc[self.cl-1] + l = [] + dlg.Update(1, u'Segments...') + for i in range(2,10) : + li = corpus.find_segments_in_classe(uces, i, 1000) + if li == [] : + break + else : + l += li + l.sort(reverse = True) + d = {} + dlg.Update(2, 'Tri...') + for i, line in enumerate(l) : + d[i] = [line[1],line[0], line[2]] + first = ['','',''] + para={'dico': d,'fline':first} + dlg.Destroy() + win = wliste(self, -1, u"Segments répétés - Classe %i" % self.cl, d, first, size=(600, 500)) + win.Show(True) + + def on_uce_carac(self,evt) : + dial = PrefUCECarac(self, self.parent) + dial.CenterOnParent() + if dial.ShowModal() == wx.ID_OK : + limite = dial.spin_eff.GetValue() + atype = dial.radio_type.GetSelection() + dlg = progressbar(self,maxi = 4) + corpus = self.Source.corpus + uces = corpus.lc[self.cl-1] + tab = corpus.make_table_with_classe(uces, self.la) + tab.pop(0) + dlg.Update(2, u'score...') + if atype == 0 : + ntab = [round(sum([self.lchi[i] for i, word in enumerate(line) if word == 1]),2) for line in tab] + else : + ntab = [round(sum([self.lchi[i] for i, word in enumerate(line) if word == 1])/float(sum(line)),2) if sum(line)!=0 else 0 for line in tab] + ntab2 = [[ntab[i], uces[i]] for i, val in enumerate(ntab)] + del ntab + ntab2.sort(reverse = True) + ntab2 = ntab2[:limite] + dlg.Update(3, u'concordancier...') + ucestxt = [corpus.ucis_paras_uces[val[1][0]][val[1][1]][val[1][2]] for val in ntab2] + ucestxt = [corpus.make_concord(self.la, ' '.join(uce), 'red') for uce in ucestxt] + dlg.Update(4, u'texte...') + ucis_txt = [' '.join(corpus.ucis[val[1][0]][0]) for val in ntab2] + win = message(self, -1, u"UCE caractéristiques - Classe %i" % self.cl, size=(600, 500), style=wx.DEFAULT_FRAME_STYLE) + win.html = '\n' + '

    '.join(['
    '.join([ucis_txt[i], 'score : ' + str(ntab2[i][0]), ucestxt[i]]) for i in range(0,len(ucestxt))]) + '\n' + win.HtmlPage.SetPage(win.html) + dlg.Destroy() + win.Show(True) + + def on_tablex(self, evt): + if 'corpus' in dir(self.Source): + corpus = self.Source.corpus + else : + corpus = self.Source.tableau + with codecs.open(self.Source.pathout['chisqtable'], 'r', corpus.parametres['syscoding']) as f : + chistable = [line.replace('\n','').replace('\r','').replace('"','').replace(',','.').split(';') for line in f] + title = chistable[0] + title.pop(0) + chistable.pop(0) + vchistable = [line[1:] for line in chistable] + fchistable = [line[0] for line in chistable] + words = [self.getColumnText(self.list.GetFirstSelected(), 6)] + tableout = [vchistable[fchistable.index(words[0])]] + last = self.list.GetFirstSelected() + while self.list.GetNextSelected(last) != -1: + last = self.list.GetNextSelected(last) + word = self.getColumnText(last, 6) + words.append(word) + tableout.append(vchistable[fchistable.index(word)]) + tmpgraph = tempfile.mktemp(dir=self.Source.parent.TEMPDIR) + nbcl = len(title) + txt = barplot(tableout, words, title, self.Source.parent.RscriptsPath['Rgraph'], tmpgraph) + tmpscript = tempfile.mktemp(dir=self.Source.parent.TEMPDIR) + file = open(tmpscript,'w') + file.write(txt) + file.close() + + exec_rcode(self.Source.parent.RPath, tmpscript, wait = True) + win = MessageImage(self, -1, u"Graphique", size=(700, 500),style = wx.DEFAULT_FRAME_STYLE) + win.addsaveimage(tmpgraph) + txt = "" % tmpgraph + win.HtmlPage.SetPage(txt) + win.Show(True) + + def onlexdendro(self, evt): + if 'corpus' in dir(self.Source): + corpus = self.Source.corpus + else : + corpus = self.Source.tableau + with codecs.open(self.Source.pathout['chisqtable'], 'r', corpus.parametres['syscoding']) as f : + chistable = [line.replace('\n','').replace('\r','').replace('"','').replace(',','.').split(';') for line in f] + title = chistable[0] + title.pop(0) + chistable.pop(0) + vchistable = [line[1:] for line in chistable] + fchistable = [line[0] for line in chistable] + words = [self.getColumnText(self.list.GetFirstSelected(), 6)] + tableout = [vchistable[fchistable.index(words[0])]] + last = self.list.GetFirstSelected() + while self.list.GetNextSelected(last) != -1: + last = self.list.GetNextSelected(last) + word = self.getColumnText(last, 6) + words.append(word) + tableout.append(vchistable[fchistable.index(word)]) + tmpgraph = tempfile.mktemp(dir=self.Source.parent.TEMPDIR) + txttable = 'c(' + ','.join([','.join(line) for line in tableout]) + ')' + rownames = 'c("' + '","'.join(words) + '")' + colnames = 'c("' + '","'.join(title) + '")' + nbcl = len(title) + rownb = len(words) + txt = """ + load("%s") + di <- matrix(data=%s, nrow=%i, byrow = TRUE) + rownames(di)<- %s + colnames(di) <- %s + library(ape) + source("%s") + height <- (30*ncol(di)) + (15*nrow(di)) + height <- ifelse(height <= 400, 400, height) + width <- 500 + open_file_graph("%s", width=width, height=height) + plot.dendro.lex(tree.cut1$tree.cl, di) + """ % (self.Source.pathout['Rdendro'], txttable, rownb, rownames, colnames, self.Source.parent.RscriptsPath['Rgraph'], ffr(tmpgraph)) + tmpscript = tempfile.mktemp(dir=self.Source.parent.TEMPDIR) + file = open(tmpscript,'w') + file.write(txt) + file.close() + exec_rcode(self.Source.parent.RPath, tmpscript, wait = True) + win = MessageImage(self, -1, u"Graphique", size=(700, 500),style = wx.DEFAULT_FRAME_STYLE) + win.addsaveimage(tmpgraph) + txt = "" % tmpgraph + win.HtmlPage.SetPage(txt) + win.Show(True) + + + def make_concord(self, uces, title, color = 'red') : + corpus = self.Source.corpus + ListWord = [self.getColumnText(self.list.GetFirstSelected(), 6)] + last = self.list.GetFirstSelected() + while self.list.GetNextSelected(last) != -1: + last = self.list.GetNextSelected(last) + ListWord.append(self.getColumnText(last, 6)) + listmot = [forme for item in ListWord for forme in corpus.getlems()[item].formes] + win = message(self, -1, title, size=(600, 500), style=wx.DEFAULT_FRAME_STYLE) + toshow = ['\n

    Concordancier

    \n'] + toshow.append('

    ' % color + ' '.join(ListWord) + '


    ') + duce = {} + ucef = [] + for word in ListWord : + ucef += list(set(corpus.getlemuces(word)).intersection(uces)) + ucef = list(set(ucef)) + ucef.sort() + res = corpus.getconcorde(ucef) + txt = '
    '.join(toshow) +'

    ' + for uce in res : + ucetxt = ' '+uce[1]+' ' + txt += ' '.join(corpus.ucis[corpus.getucefromid(uce[0]).uci].etoiles) + '
    ' + for forme in listmot: + forme = corpus.getforme(forme).forme + ucetxt = ucetxt.replace(' '+forme+' ', ' ' + forme + ' ') + txt += ucetxt + '

    ' + win.HtmlPage.SetPage(txt) + return win + + def OnPopupTwo(self, event): + corpus = self.Source.corpus + uces = corpus.lc[self.cl-1] + win = self.make_concord(uces, "Concordancier - Classe %i" % self.cl) + win.Show(True) + + def OnPopupThree(self, event): + corpus = self.Source.corpus + uces = [classe[i] for classe in corpus.lc for i in range(0,len(classe))] + win = self.make_concord(uces, "Concordancier - UCE classées") + win.Show(True) + + def OnPopupFour(self, event): + corpus = self.Source.corpus + uces = [classe[i] for classe in corpus.lc for i in range(0,len(classe))] + corpus.lc0 + win = self.make_concord(uces, "Concordancier - Toutes les UCE") + win.Show(True) + + def OnPopupFive(self, event): + word = self.getColumnText(self.list.GetFirstSelected(), 6) + lk = "http://www.cnrtl.fr/definition/" + word + webbrowser.open(lk) + + def OnPopupSix(self, event): + word = self.getColumnText(self.list.GetFirstSelected(), 6) + lk = "http://www.cnrtl.fr/etymologie/" + word + webbrowser.open(lk) + + def OnPopupSeven(self, event): + word = self.getColumnText(self.list.GetFirstSelected(), 6) + lk = "http://www.cnrtl.fr/synonymie/" + word + webbrowser.open(lk) + + def OnPopupHeight(self, event): + word = self.getColumnText(self.list.GetFirstSelected(), 6) + lk = "http://www.cnrtl.fr/antonymie/" + word + webbrowser.open(lk) + + def OnPopupNine(self, event): + word = self.getColumnText(self.list.GetFirstSelected(), 6) + lk = "http://www.cnrtl.fr/morphologie/" + word + webbrowser.open(lk) + + def onproxe(self, evt) : + word = self.getColumnText(self.list.GetFirstSelected(), 6) + lk = "http://www.cnrtl.fr/proxemie/" + word + webbrowser.open(lk) + + def OnSize(self, event): + w, h = self.GetClientSizeTuple() + self.list.SetDimensions(0, 0, w, h) + + def OnColClick(self, event): + self.do_greyline() + + +class wliste(wx.Frame): + def __init__(self, parent, id, title, d, fline, size=(600, 500)): + wx.Frame.__init__(self, parent, id) + self.liste = ListForSpec(self, parent, d, fline) + self.button_1 = wx.Button(self, -1, "Fermer") + self.Bind(wx.EVT_BUTTON, self.OnCloseMe, self.button_1) + self.Bind(wx.EVT_CLOSE, self.OnCloseWindow) + self.__do_layout() + + def __do_layout(self): + sizer_1 = wx.BoxSizer(wx.VERTICAL) + sizer_2 = wx.BoxSizer(wx.VERTICAL) + sizer_2.Add(self.liste, 1, wx.EXPAND | wx.ADJUST_MINSIZE, 0) + sizer_2.Add(self.button_1, 0, wx.ALIGN_CENTER_HORIZONTAL | wx.ADJUST_MINSIZE, 0) + sizer_1.Add(sizer_2, 1, wx.EXPAND, 0) + self.SetAutoLayout(True) + self.SetSizer(sizer_1) + self.Layout() + + def OnCloseMe(self, event): + self.Close(True) + + def OnCloseWindow(self, event): + self.Destroy() + +class message(wx.Frame): + def __init__(self, *args, **kwds): + kwds["style"] = wx.DEFAULT_FRAME_STYLE + wx.Frame.__init__(self, *args, **kwds) + self.html = "" + self.HtmlPage=wx.html.HtmlWindow(self, -1) + if "gtk2" in wx.PlatformInfo: + self.HtmlPage.SetStandardFonts() + self.HtmlPage.SetFonts('Courier','Courier') + self.button_1 = wx.Button(self, -1, "Fermer") + self.button_2 = wx.Button(self, -1, u"Enregistrer...") + + self.Bind(wx.EVT_BUTTON, self.OnSavePage, self.button_2) + self.Bind(wx.EVT_BUTTON, self.OnCloseMe, self.button_1) + self.Bind(wx.EVT_CLOSE, self.OnCloseWindow) + self.__do_layout() + + def __do_layout(self): + sizer_1 = wx.BoxSizer(wx.VERTICAL) + sizer_2 = wx.BoxSizer(wx.VERTICAL) + sizer_2.Add(self.HtmlPage, 1, wx.EXPAND|wx.ADJUST_MINSIZE, 0) + sizer_2.Add(self.button_1, 0, wx.ALIGN_CENTER_HORIZONTAL|wx.ADJUST_MINSIZE, 0) + sizer_2.Add(self.button_2, 0, wx.ALIGN_CENTER_HORIZONTAL|wx.ADJUST_MINSIZE, 0) + sizer_1.Add(sizer_2, 1, wx.EXPAND, 0) + self.SetAutoLayout(True) + self.SetSizer(sizer_1) + self.Layout() + + def OnSavePage(self, evt) : + dlg = wx.FileDialog( + self, message="Enregistrer sous...", defaultDir=os.getcwd(), + defaultFile="concordancier.html", wildcard="html|*.html", style=wx.SAVE | wx.OVERWRITE_PROMPT + ) + dlg.SetFilterIndex(2) + dlg.CenterOnParent() + if dlg.ShowModal() == wx.ID_OK: + path = dlg.GetPath() + with open(path, 'w') as f : + f.write(self.html) + + def OnCloseMe(self, event): + self.Close(True) + + def OnCloseWindow(self, event): + self.Destroy() + +def getSmallUpArrowData(): + return \ +'\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x10\x00\x00\x00\x10\x08\x06\ +\x00\x00\x00\x1f\xf3\xffa\x00\x00\x00\x04sBIT\x08\x08\x08\x08|\x08d\x88\x00\ +\x00\x00C\xb0\x89\ +\xd3.\x10\xd1m\xc3\xe5*\xbc.\x80i\xc2\x17.\x8c\xa3y\x81\x01\x00\xa1\x0e\x04e\ +?\x84B\xef\x00\x00\x00\x00IEND\xaeB`\x82" + +def getSmallDnArrowBitmap(): + return wx.BitmapFromImage(getSmallDnArrowImage()) + +def getSmallDnArrowImage(): + stream = cStringIO.StringIO(getSmallDnArrowData()) + return wx.ImageFromStream(stream) diff --git a/Rscripts/CHD.R b/Rscripts/CHD.R new file mode 100644 index 0000000..fcec03f --- /dev/null +++ b/Rscripts/CHD.R @@ -0,0 +1,389 @@ +#Author: Pierre Ratinaud +#Copyright (c) 2008-2011 Pierre Ratinaud +#Lisense: GNU/GPL + +pp<-function(txt,val) { + d<-paste(txt,' : ') + print(paste(d,val)) +} +MyChiSq<-function(x,sc,n){ + sr<-rowSums(x) + E <- outer(sr, sc, "*")/n + STAT<-sum(((x - E)^2)/E) + STAT +} + +MySpeedChi <- function(x,sc) { + sr <-rowSums(x) + E <- outer(sr, sc, "*") + STAT<-sum((x - E)^2/E) + STAT +} + +find.max <- function(dtable, chitable, compte, rmax, maxinter, sc, TT) { + ln <- which(dtable==1, arr.ind=TRUE) + lo <- list() + lo[1:nrow(dtable)] <- 0 + for (k in 1:nrow(ln)) {lo[[ln[k,1]]]<-append(lo[[ln[k,1]]],ln[k,2])} + for (k in 1:nrow(dtable)) {lo[[k]] <- lo[[k]][-1]} + lo<-lo[-c(1,length(lo))] + for (l in lo) { + compte <- compte + 1 + chitable[1,l]<-chitable[1,l]+1 + chitable[2,l]<-chitable[2,l]-1 + chi<-MyChiSq(chitable,sc,TT) + if (chi>maxinter) { + maxinter<-chi + rmax<-compte + } + } + res <- list(maxinter=maxinter, rmax=rmax) + res +} + +CHD<-function(data.in, x=9, libsvdc=FALSE, libsvdc.path=NULL){ +# sink('/home/pierre/workspace/iramuteq/dev/findchi2.txt') + dataori <- data.in + row.names(dataori) <- rownames(data.in) + dtable <- data.in + colnames(dtable) <- 1:ncol(dtable) + dout <- NULL + rowelim<-NULL + pp('ncol entree : ',ncol(dtable)) + pp('nrow entree',nrow(dtable)) + listcol <- list() + listmere <- list() + list_fille <- list() + print('vire colonnes vides en entree')#FIXME : il ne doit pas y avoir de colonnes vides en entree !! + sdt<-colSums(dtable) + if (min(sdt)==0) + dtable<-dtable[,-which(sdt==0)] + print('vire lignes vides en entree') + sdt<-rowSums(dtable) + if (min(sdt)==0) { + rowelim<-as.integer(rownames(dtable)[which(sdt==0)]) + print('&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&') + print(rowelim) + print('&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&') + dtable<-dtable[-which(sdt==0),] + } + mere<-1 + for (i in 1:x) { + clnb<-(i*2) + listmere[[clnb]]<-mere + listmere[[clnb+1]]<-mere + list_fille[[mere]] <- c(clnb,clnb+1) + listcol[[clnb]]<-vector() + listcol[[clnb+1]]<-vector() + #extraction du premier facteur de l'afc + print('afc') + pp('taille dtable dans boucle (col/row)',c(ncol(dtable),nrow(dtable))) + afc<-boostana(dtable, nd=1, libsvdc=libsvdc, libsvdc.path=libsvdc.path) + pp('SV',afc$singular.values) + pp('V.P.', afc$eigen.values) + coordrow <- as.matrix(afc$row.scores[,1]) + coordrowori<-coordrow + row.names(coordrow)<-rownames(dtable) + coordrow <- cbind(coordrow,1:nrow(dtable)) + print('deb recherche meilleur partition') + ordert <- as.matrix(coordrow[order(coordrow[,1]),]) + ordert <- cbind(ordert, 1:nrow(ordert)) + ordert <- ordert[order(ordert[,2]),] + + listinter<-vector() + listlim<-vector() + dtable <- dtable[order(ordert[,3]),] + sc <- colSums(dtable) + TT <- sum(sc) + sc1 <- dtable[1,] + sc2 <- colSums(dtable) - sc1 + chitable <- rbind(sc1, sc2) + compte <- 1 + maxinter <- 0 + rmax <- NULL + + inert <- find.max(dtable, chitable, compte, rmax, maxinter, sc, TT) + print('@@@@@@@@@@@@@@@@@@@@@@@@@@@@') + pp('max inter phase 1', inert$maxinter/TT)#max(listinter)) + print('@@@@@@@@@@@@@@@@@@@@@@@@@@@@') + ordert <- ordert[order(ordert[,3]),] + listclasse<-ifelse(coordrowori<=ordert[(inert$rmax),1],clnb,clnb+1) + dtable <- dtable[order(ordert[,2]),] + cl<-listclasse + pp('TT',TT) + #dtable<-cbind(dtable,'cl'= as.vector(cl)) + + N1<-length(listclasse[listclasse==clnb]) + N2<-length(listclasse[listclasse==clnb+1]) + pp('N1',N1) + pp('N2',N2) +################################################################### +# reclassement des individus # +################################################################### + malcl<-1000000000000 + it<-0 + listsub<-list() + #in boucle + ln <- which(dtable==1, arr.ind=TRUE) + lnz <- list() + lnz[1:nrow(dtable)] <- 0 + + for (k in 1:nrow(ln)) {lnz[[ln[k,1]]]<-append(lnz[[ln[k,1]]],ln[k,2])} + for (k in 1:nrow(dtable)) {lnz[[k]] <- lnz[[k]][-1]} + TT<-sum(dtable) + + while (malcl!=0 & N1>=5 & N2>=5) { + it<-it+1 + listsub[[it]]<-vector() + txt <- paste('nombre iteration', it) + #pp('nombre iteration',it) + vdelta<-vector() + #dtable[,'cl']<-cl + t1<-dtable[which(cl[,1]==clnb),]#[,-ncol(dtable)] + t2<-dtable[which(cl[,1]==clnb+1),]#[,-ncol(dtable)] + ncolt<-ncol(t1) + #pp('ncolt',ncolt) + + if (N1 != 1) { + sc1<-colSums(t1) + } else { + sc1 <- t1 + } + if (N2 != 1) { + sc2<-colSums(t2) + } else { + sc2 <- t2 + } + + sc<-sc1+sc2 + chtableori<-rbind(sc1,sc2) + chtable<-chtableori + interori<-MyChiSq(chtableori,sc,TT)/TT#chisq.test(chtableori)$statistic#/TT + txt <- paste(txt, ' - interori : ',interori) + #pp('interori',interori) + + N1<-nrow(t1) + N2<-nrow(t2) + + #pp('N1',N1) + #pp('N2',N2) + txt <- paste(txt, 'N1:', N1,'-N2:',N2) + print(txt) + compte <- 0 + for (l in lnz){ + chi.in<-chtable + compte <- compte + 1 + if(cl[compte]==clnb){ + chtable[1,l]<-chtable[1,l]-1 + chtable[2,l]<-chtable[2,l]+1 + }else{ + chtable[1,l]<-chtable[1,l]+1 + chtable[2,l]<-chtable[2,l]-1 + } + interswitch<-MyChiSq(chtable,sc,TT)/TT#chisq.test(chtable)$statistic/TT + ws<-interori-interswitch + + if (ws<0){ + interori<-interswitch + if(cl[compte]==clnb){ + #sc1<-chtable[1,] + #sc2<-chtable[2,] + cl[compte]<-clnb+1 + listsub[[it]]<-append(listsub[[it]],compte) + } else { + #sc1<-chtable[1,] + #sc2<-chtable[2,] + cl[compte]<-clnb + listsub[[it]]<-append(listsub[[it]],compte) + } + vdelta<-append(vdelta,compte) + } else { + chtable<-chi.in + } + } +# for (val in vdelta) { +# if (cl[val]==clnb) { +# cl[val]<-clnb+1 +# listsub[[it]]<-append(listsub[[it]],val) +# }else { +# cl[val]<-clnb +# listsub[[it]]<-append(listsub[[it]],val) +# } +# } + print('###################################') + print('longueur < 0') + malcl<-length(vdelta) + if ((it>1)&&(!is.logical(listsub[[it]]))&&(!is.logical(listsub[[it-1]]))){ + if (listsub[[it]]==listsub[[(it-1)]]){ + malcl<-0 + } + } + print(malcl) + print('###################################') + } + #dtable<-cbind(dtable,'cl'=as.vector(cl)) + #dtable[,'cl'] <-as.vector(cl) +####################################################################### +# Fin reclassement des individus # +####################################################################### +# if (!(length(cl[cl==clnb])==1 || length(cl[cl==clnb+1])==1)) { + #t1<-dtable[dtable[,'cl']==clnb,][,-ncol(dtable)] + #t2<-dtable[dtable[,'cl']==clnb+1,][,-ncol(dtable)] + t1<-dtable[which(cl[,1]==clnb),]#[,-ncol(dtable)] + t2<-dtable[which(cl[,1]==clnb+1),]#[,-ncol(dtable)] + if (class(t1)=='numeric') { + sc1 <- as.vector(t1) + nrowt1 <- 1 + } else { + sc1 <- colSums(t1) + nrowt1 <- nrow(t1) + } + if (class(t2)=='numeric') { + sc2 <- as.vector(t2) + nrowt2 <- 1 + } else { + sc2 <- colSums(t2) + nrowt2 <- nrow(t2) + } + chtable<-rbind(sc1,sc2) + inter<-chisq.test(chtable)$statistic/TT + pp('last inter',inter) + print('=====================') + #calcul de la specificite des colonnes + mint<-min(nrowt1,nrowt2) + maxt<-max(nrowt1,nrowt2) + seuil<-round((1.9*(maxt/mint))+1.9,digit=6) + #sink('/home/pierre/workspace/iramuteq/dev/findchi2.txt') +# print('ATTENTION SEUIL 3,84') +# seuil<-3.84 + pp('seuil',seuil) + sominf<-0 + nv<-0 + nz<-0 + ncclnb<-0 + ncclnbp<-0 + NN1<-0 + NN2<-0 + maxchip<-0 + nbzeroun<-0 + res1<-0 + res2<-0 + nbseuil<-0 + nbexe<-0 + nbcontrib<-0 + cn<-colnames(dtable) + #another try######################################### + one <- cbind(sc1,sc2) + cols <- c(length(which(cl==clnb)), length(which(cl==clnb+1))) + print(cols) + colss <- matrix(rep(cols,ncol(dtable)), ncol=2, byrow=TRUE) + zero <- colss - one + rows <- cbind(rowSums(zero), rowSums(one)) + n <- sum(cols) + for (m in 1:nrow(rows)) { + obs <- t(matrix(c(zero[m,],one[m,]),2,2)) + E <- outer(rows[m,],cols,'*')/n + if ((min(obs[2,])==0) & (min(obs[1,])!=0)) { + chi <- seuil + 1 + } else if ((min(obs[1,])==0) & (min(obs[2,])!=0)) { + chi <- seuil - 1 + } else if (any(obs < 10)) { + chi <- sum((abs(obs - E) - 0.5)^2 / E) + } else { + chi <- sum(((obs - E)^2)/E) + } + if (is.na(chi)) { + chi <- 0 + } + if (chi > seuil) { + if (obs[2,1] < E[2,1]) { + listcol[[clnb]]<-append(listcol[[clnb]],cn[m]) + ncclnb<-ncclnb+1 + } else if (obs[2,2] < E[2,2]) { + listcol[[clnb+1]]<-append(listcol[[clnb+1]],cn[m]) + ncclnbp<-ncclnbp+1 + } + } + } + ###################################################### + print('resultats elim item') + pp(clnb+1,length(listcol[[clnb+1]])) + pp(clnb,length(listcol[[clnb]])) + pp('ncclnb',ncclnb) + pp('ncclnbp',ncclnbp) + listrownamedtable<-rownames(dtable) + listrownamedtable<-as.integer(listrownamedtable) + newcol<-vector(length=nrow(dataori)) + #remplissage de la nouvelle colonne avec les nouvelles classes + print('remplissage') +# num<-0 + newcol[listrownamedtable] <- cl[,1] + #recuperation de la classe precedante pour les cases vides + print('recuperation classes precedentes') + if (i!=1) { + newcol[which(newcol==0)] <- dout[,ncol(dout)][which(newcol==0)] + } + if(!is.null(rowelim)) { + newcol[rowelim] <- 0 + } + tailleclasse<-as.matrix(summary(as.factor(as.character(newcol)))) + print('tailleclasse') + print(tailleclasse) + tailleclasse<-as.matrix(tailleclasse[!(rownames(tailleclasse)==0),]) + plusgrand<-which.max(tailleclasse) + #??????????????????????????????????? + #Si 2 classes ont des effectifs egaux, on prend la premiere de la liste... + if (length(plusgrand)>1) { + plusgrand<-plusgrand[1] + } + #???????????????????????????????????? + + #constuction du prochain tableau a analyser + print('construction tableau suivant') + dout<-cbind(dout,newcol) + classe<-as.integer(rownames(tailleclasse)[plusgrand]) + dtable<-dataori[which(newcol==classe),] + row.names(dtable)<-rownames(dataori)[which(newcol==classe)] + colnames(dtable) <- 1:ncol(dtable) + mere<-classe + listcolelim<-listcol[[as.integer(classe)]] + mother<-listmere[[as.integer(classe)]] + while (mother!=1) { + listcolelim<-append(listcolelim,listcol[[mother]]) + mother<-listmere[[mother]] + } + + listcolelim<-sort(unique(listcolelim)) + pp('avant',ncol(dtable)) + if (!is.logical(listcolelim)){ + print('elimination colonne') + #dtable<-dtable[,-listcolelim] + dtable<-dtable[,!(colnames(dtable) %in% listcolelim)] + } + pp('apres',ncol(dtable)) + #elimination des colonnes ne contenant que des 0 + print('vire colonne inf 3 dans boucle') + sdt<-colSums(dtable) + if (min(sdt)<=3) + dtable<-dtable[,-which(sdt<=3)] + + #elimination des lignes ne contenant que des 0 + print('vire ligne vide dans boucle') + if (ncol(dtable)==1) { + sdt<-dtable[,1] + } else { + sdt<-rowSums(dtable) + } + if (min(sdt)==0) { + rowelim<-as.integer(rownames(dtable)[which(sdt==0)]) + print('&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&') + print(rowelim) + print('&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&') + dtable<-dtable[-which(sdt==0),] + } +# } + } +# sink() + res <- list(n1 = dout, list_mere = listmere, list_fille = list_fille) + res +} diff --git a/Rscripts/CHD.R.old b/Rscripts/CHD.R.old new file mode 100644 index 0000000..f81e346 --- /dev/null +++ b/Rscripts/CHD.R.old @@ -0,0 +1,245 @@ +#library(ca) +#library(MASS) +#source('/home/pierre/workspace/iramuteq/Rscripts/afc.R') +#data<-read.table('output/corpus_bin.csv',header=TRUE,sep='\t') +source('/home/pierre/workspace/iramuteq/Rscripts/anacor.R') + +CHD<-function(data,x=9){ + dataori=data + dtable=data + listcol<-list() + listmere<-list() + a<-0 + print('vire colonnes vides en entree')#FIXME : il ne doit pas y avoir de colonnes vides en entree !! + for (m in 1:length(dtable)) { + if (sum(dtable[m-a])==0) { + print('colonne vide') + dtable<-dtable[,-(m-a)] + a<-a+1 + } + } + for (i in 1:x) { + clnb<-(i*2) + listmere[[clnb]]<-i + listmere[[clnb+1]]<-i + listcol[[clnb]]<-vector() + listcol[[clnb+1]]<-vector() + #extraction du premier facteur de l'afc + print('afc') + #afc<-ca(dtable,nd=1) + #afc<-corresp(dtable,nd=1) + #afc<-fca(dtable) + afc<-boostana(dtable,nd=1) + #coordonnees des colonnes sur le premier facteur + #coordrow=afc$rowcoord + #coordrow=as.matrix(afc$rscore) + #coordrow<-as.matrix(afc$rproj[,1]) + coordrow<-as.matrix(afc$row.scores) + #row.names(coordrow)<-afc$rownames + row.names(coordrow)<-rownames(dtable) + #classement en fonction de la position sur le premier facteur + #listclasse<-ifelse(coordrow<0,paste('CLASSE',clnb,sep=''),paste('CLASSE',clnb+1,sep='')) + + print('deb recherche meilleur partition') + coordrow<-as.matrix(coordrow[order(coordrow[,1]),]) + #print(rownames(coordrow)) + zeropoint<-which.min(abs(coordrow)) + print(zeropoint) + g<-length(coordrow[coordrow[,1]coordrow[zeropoint]]) + prct<-1 + g<-round(g*prct) + d<-round(d*prct) + print(g) + print(d) + temptable<-as.matrix(coordrow[(zeropoint-g):(zeropoint+d)]) + row.names(temptable)<-rownames(coordrow)[(zeropoint-g):(zeropoint+d)] + #print(temptable) + missing<-zeropoint-g + listchi<-vector() + chtable<-matrix(0,2,(ncol(dtable))) + totforme<-chtable[1,] + for (forme in 1:(ncol(dtable))) { + totforme[forme]<-sum(dtable[,forme]) + } + chtable[2,]<-totforme + for (l in 1:length(temptable)) { + # print(rownames(temptable)[l]) + linetoswitch=as.matrix(dtable[rownames(temptable)[l],]) + # print(linetoswitch) + chtable[1,]<-chtable[1,]+linetoswitch + chtable[2,]<-chtable[2,]-linetoswitch + valchi<-chisq.test(chtable)$statistic + if (is.na(valchi)){ + valchi<-0 + } + listchi<-append(listchi,valchi) + } + #listchi<-listchi[!is.na(listchi)] + maxchi<-which(listchi==max(listchi)) + print(max(listchi)) + print(maxchi) + maxchi<-maxchi+missing + #print(listchi) + #listclasse + print('liste classe') + print(coordrow[(maxchi)]) + listclasse<-ifelse(coordrow<=coordrow[(maxchi)],clnb,clnb+1) +# listclasse<-ifelse(coordrow<0,clnb,clnb+1) + listchi<-as.matrix(listchi) + listchi<-cbind(listchi,temptable) + filename<-paste('graphechi',as.character(i)) + filename<-paste(filename,'.jpeg') + jpeg(filename) + plot(listchi[,1]~listchi[,2]) + abline(v=0) + print(coordrow[zeropoint-g]) + abline(v=coordrow[zeropoint-g]) + abline(v=coordrow[zeropoint+d]) + abline(v=coordrow[(maxchi)]) + dev.off() + + #ajout du classement au tableau + dtable<-transform(dtable,cl1=listclasse) + + #calcul de la specificite des colonnes + t1<-dtable[dtable$cl1==clnb,] + t2<-dtable[dtable$cl1==clnb+1,] + + for (k in 1:(ncol(dtable)-1)) { + t<-matrix(0,2,2) + t[1,1]<-sum(t1[,k]) + t[1,2]<-sum(t2[,k]) + t[2,1]<-nrow(t1)-t[1,1] + t[2,2]<-nrow(t2)-t[1,2] + chi<-chisq.test(t) + if (chi$statistic>6){#FIXME : valeur a mettre en option base :2.7 + if (chi$expected[1,1]1) { + plusgrand<-plusgrand[1] + } + #???????????????????????????????????? + + #constuction du prochain tableau a analyser + print('construction tableau suivant') + classe<-classes[plusgrand] + dtable<-dataori[dataori[length(dataori)]==classe,] + dtable<-dtable[,1:(length(dtable)-i)] + + + listcolelim<-listcol[[as.integer(classe)]] + mother<-listmere[[as.integer(classe)]] + while (mother!=1) { + listcolelim<-append(listcolelim,listcol[[mother]]) + print(listcolelim) + mother<-listmere[[mother]] + } + + listcolelim<-sort(unique(listcolelim)) + print(listcolelim) + print('avant') + print(ncol(dtable)) + if (!is.logical(listcolelim)){ + print('elimination colonne') + a<-0 + for (col in listcolelim){ + dtable<-dtable[,-(col-a)] + a<-a+1 + } + } + print('apres') + print(ncol(dtable)) + #elimination des colonnes ne contenant que des 0 + print('vire colonne vide dans boucle') + a<-0 + for (m in 1:ncol(dtable)) { + if (sum(dtable[,m-a])==0) { + dtable<-dtable[,-(m-a)] + a<-a+1 + } + } + #elimination des lignes ne contenant que des 0 +# print('vire ligne vide dans boucle') +# a<-0 +# for (m in 1:nrow(dtable)) { +# if (sum(dtable[m-a,])==0) { +# print('ligne vide') +# dtable<-dtable[-(m-a),] +# a<-a+1 +# } +# } + } + dataori[(length(dataori)-x+1):length(dataori)] +} + +#dataout<-CHD(data,9) + +#library(cluster) +#dissmat<-daisy(dataout, metric = 'gower', stand = FALSE) +#chd<-diana(dissmat,diss=TRUE,) + + +#pour tester le type, passer chaque colonne en matice et faire mode(colonne) +#for (i in 1:13) {tmp<-as.matrix(data[i]);print(mode(tmp))} diff --git a/Rscripts/CHDKmeans.R b/Rscripts/CHDKmeans.R new file mode 100644 index 0000000..c0a0d01 --- /dev/null +++ b/Rscripts/CHDKmeans.R @@ -0,0 +1,471 @@ +#Author: Pierre Ratinaud +#Copyright (c) 2008 Pierre Ratinaud +#Lisense: GNU/GPL + + + +#library(ca) +#library(MASS) +#source('/home/pierre/workspace/iramuteq/Rscripts/afc.R') +#data<-read.table('output/corpus_bin.csv',header=TRUE,sep='\t') +#source('/home/pierre/workspace/iramuteq/Rscripts/anacor.R') +#library(ade4) +pp<-function(txt,val) { + d<-paste(txt,' : ') + print(paste(d,val)) +} +MyChiSq<-function(x,sc,n){ + sr<-rowSums(x) + E <- outer(sr, sc, "*")/n + STAT<-sum((abs(x - E))^2/E) + STAT +} + +CHD<-function(data,x=9){ +# sink('/home/pierre/workspace/iramuteq/dev/findchi2.txt') + dataori=as.matrix(data) + row.names(dataori)=rownames(data) + dtable=as.matrix(data) + row.names(dtable)=rownames(data) + rowelim<-NULL + pp('ncol entree : ',ncol(dtable)) + pp('nrow entree',nrow(dtable)) + listcol <- list() + listmere <- list() + list_fille <- list() + print('vire colonnes vides en entree')#FIXME : il ne doit pas y avoir de colonnes vides en entree !! + sdt<-colSums(dtable) + if (min(sdt)==0) + dtable<-dtable[,-which(sdt==0)] + mere<-1 + for (i in 1:x) { + clnb<-(i*2) + listmere[[clnb]]<-mere + listmere[[clnb+1]]<-mere + list_fille[[mere]] <- c(clnb,clnb+1) + listcol[[clnb]]<-vector() + listcol[[clnb+1]]<-vector() + #extraction du premier facteur de l'afc + print('afc') + #afc<-ca(dtable,nd=1) + #afc<-corresp(dtable,nd=1) + #afc<-fca(dtable) + pp('taille dtable dans boucle (col/row)',c(ncol(dtable),nrow(dtable))) +# afc<-boostana(dtable,nd=1) +# #afc<-dudi.coa(dtable,scannf=FALSE,nf=1) +# pp('SV',afc$singular.values) +# pp('V.P.', afc$eigen.values) +# #pp('V.P.',afc$eig[1]) +# #pp('V.P.',afc$factexpl[1]) +# #print(afc$chisq) +# afcchi<-chisq.test(dtable) +# Tinert<-afcchi$statistic/sum(dtable) +# pp('inertie totale',Tinert) +# #coordonnees des colonnes sur le premier facteur +# #coordrow=afc$rowcoord +# coordrow=as.matrix(afc$row.scores) +# #coordrow<-as.matrix(afc$rproj[,1]) +# #coordrow<-as.matrix(afc$li) +# coordrowori<-coordrow +# #row.names(coordrow)<-afc$rownames +# row.names(coordrow)<-rownames(dtable) +# #classement en fonction de la position sur le premier facteur +# print('deb recherche meilleur partition') +# ordertable<-cbind(dtable,coordrow) +# coordrow<-as.matrix(coordrow[order(coordrow[,1]),]) +# ordertable<-ordertable[order(ordertable[,ncol(ordertable)]),][,-ncol(ordertable)] +# listinter<-vector() +# listlim<-vector() + TT=sum(dtable) +# sc<-colSums(dtable) +# sc1<-ordertable[1,] +# sc2<-colSums(dtable)-ordertable[1,] +# chitable<-rbind(sc1,sc2) +# #listinter<-vector() +# maxinter<-0 +# rmax<-NULL +################################################################## +# for (l in 2:(nrow(dtable)-2)){ +# chitable[1,]<-chitable[1,]+ordertable[l,] +# chitable[2,]<-chitable[2,]-ordertable[l,] +## sc1<-sc1+ordertable[l,] +## sc2<-sc2-ordertable[l,] +## chitable<-rbind(sc1,sc2) +# chi<-MyChiSq(chitable,sc,TT) +# if (chi>maxinter) { +# maxinter<-chi +# rmax<-l +# } +# # listinter<-append(listinter,MyChiSq(chitable,sc,TT))#,chisq.test(chitable)$statistic/TT) +# } +# #plot(listinter) +# print('@@@@@@@@@@@@@@@@@@@@@@@@@@@@') +# pp('max inter phase 1', maxinter/TT)#max(listinter)) +# print('@@@@@@@@@@@@@@@@@@@@@@@@@@@@') +# #maxinter<-which.max(listinter)+1 +# +# listclasse<-ifelse(coordrowori<=coordrow[(rmax),1],clnb,clnb+1) +# +# cl<-listclasse +# +# pp('TT',TT) +# #dtable<-cbind(dtable,'cl'= as.vector(cl)) +# +# N1<-length(listclasse[listclasse==clnb]) +# N2<-length(listclasse[listclasse==clnb+1]) +# pp('N1',N1) +# pp('N2',N2) +#################################################################### +## reclassement des individus # +#################################################################### +# malcl<-1000000000000 +# it<-0 +# listsub<-list() +# #in boucle +# +# while (malcl!=0 & N1>=5 & N2>=5) { +# it<-it+1 +# listsub[[it]]<-vector() +# pp('nombre iteration',it) +# vdelta<-vector() +# #dtable[,'cl']<-cl +# t1<-dtable[cl==clnb,]#[,-ncol(dtable)] +# t2<-dtable[cl==clnb+1,]#[,-ncol(dtable)] +# ncolt<-ncol(t1) +# pp('ncolt',ncolt) +# sc1<-colSums(t1) +# sc2<-colSums(t2) +# sc<-sc1+sc2 +# TT<-sum(dtable) +# chtableori<-rbind(sc1,sc2) +# interori<-MyChiSq(chtableori,sc,TT)/TT#chisq.test(chtableori)$statistic#/TT +# chtable<-chtableori +# pp('interori',interori) +# N1<-nrow(t1) +# N2<-nrow(t2) +# pp('N1',N1) +# pp('N2',N2) +# +# for (l in 1:nrow(dtable)){ +# if(cl[l]==clnb){ +# chtable[1,]<-sc1-dtable[l,] +# chtable[2,]<-sc2+dtable[l,] +# }else{ +# chtable[1,]<-sc1+dtable[l,] +# chtable[2,]<-sc2-dtable[l,] +# } +# interswitch<-MyChiSq(chtable,sc,TT)/TT#chisq.test(chtable)$statistic/TT +# ws<-interori-interswitch +# +# if (ws<0){ +# interori<-interswitch +# if(cl[l]==clnb){ +# sc1<-chtable[1,] +# sc2<-chtable[2,] +# cl[l]<-clnb+1 +# listsub[[it]]<-append(listsub[[it]],l) +# }else{ +# sc1<-chtable[1,] +# sc2<-chtable[2,] +# cl[l]<-clnb +# listsub[[it]]<-append(listsub[[it]],l) +# } +# vdelta<-append(vdelta,l) +# } +# } +## for (val in vdelta) { +## if (cl[val]==clnb) { +## cl[val]<-clnb+1 +## listsub[[it]]<-append(listsub[[it]],val) +## }else { +## cl[val]<-clnb +## listsub[[it]]<-append(listsub[[it]],val) +## } +## } +# print('###################################') +# print('longueur < 0') +# malcl<-length(vdelta) +# if ((it>1)&&(!is.logical(listsub[[it]]))){ +# if (listsub[[it]]==listsub[[(it-1)]]){ +# malcl<-0 +# } +# } +# print(malcl) +# print('###################################') +# } +########################################################################## +# kmeans +# clori<-kmeans(dtable,2)$cluster +# cl<-ifelse(clori==1,clnb,clnb+1) +#pam + library(cluster) + clori<-pam(dist(dtable,method='binary'),2,diss=TRUE)$cluster + cl<-ifelse(clori==1,clnb,clnb+1) + + dtable<-cbind(dtable,'cl'=as.vector(cl)) + +####################################################################### +# Fin reclassement des individus # +####################################################################### +# if (!(length(cl[cl==clnb])==1 || length(cl[cl==clnb+1])==1)) { + t1<-dtable[dtable[,'cl']==clnb,][,-ncol(dtable)] + t2<-dtable[dtable[,'cl']==clnb+1,][,-ncol(dtable)] + + chtable<-rbind(colSums(t1),colSums(t2)) + inter<-chisq.test(chtable)$statistic/TT + pp('last inter',inter) + print('=====================') + + #calcul de la specificite des colonnes + mint<-min(nrow(t1),nrow(t2)) + maxt<-max(nrow(t1),nrow(t2)) + seuil<-round((1.9*(maxt/mint))+1.9,digit=6) + #sink('/home/pierre/workspace/iramuteq/dev/findchi2.txt') +# print('ATTENTION SEUIL 3,84') +# seuil<-3.84 + pp('seuil',seuil) + sominf<-0 + nv<-0 + nz<-0 + ncclnb<-0 + ncclnbp<-0 + NN1<-0 + NN2<-0 + maxchip<-0 + nbzeroun<-0 + res1<-0 + res2<-0 + nbseuil<-0 + nbexe<-0 + nbcontrib<-0 + cn<-colnames(dtable)[1:(ncol(dtable)-1)] + for (k in 1:(ncol(dtable)-1)) { + #print(k) + tab<-table(dtable[,k],dtable[,ncol(dtable)]) + #print(cn[k]) + #print (tab) + #chidemoi<-(sum(t)*(((t[1,1]*t[2,2])-(t[1,2]*t[2,1]))^2))/((rowSums(t)[1])*(rowSums(t)[2])*(colSums(t)[1])*(colSums(t)[2])) + if (rownames(tab)[1]==1 & nrow(tab)==1) { + tab<-rbind(c(0,0),tab[1]) + print('tableau vide dessus') + } + if (min(tab)<=10){ + chi<-chisq.test(tab,correct=TRUE) + }else{ + chi<-chisq.test(tab,correct=FALSE) + } + if ((min(tab[2,])==1) & (min(tab[1,])!=0)){ + chi$statistic<-seuil+1 + # print('min tab 2 == 0') + } +# if(((tab[2,1]>tab[1,1]) & (tab[2,2]>tab[1,2]))){ +# nz<-nz+1 +# chi$statistic<-seuil-1 +# sominf<-sominf-1 +# } + if ((min(tab[1,])==0) & (min(tab[2,])!=0)){ + chi$statistic<-seuil-1 + # print('min tab 1 == 0') + } + if (is.na(chi$statistic)) { + chi$statistic<-0 + print('NA dans chi$statistic') + } +# print('#####################') + if (chi$statistic>seuil){ + i# print('sup seuil') + if (tab[2,1]=seuil) { +# if (which.max(chit)==2) NN1<-NN1+1 +# if (which.max(chit)==4) NN2<-NN2+1 +# } +# if (max(abs(contrib[2,])>=1.96)) { +# nbcontrib<-nbcontrib+1 +# } +# if (max(chit[2,]>=seuil)) maxchip<-maxchip+1 +# if (chi$statistic >=seuil) nbseuil<-nbseuil+1 +# if ((min(tab[2,])==0) & (chi$statistic=seuil) { +# if ((which.max(chi$residual)==2) || (which.min(chi$residual)==4)) res1<-res1+1 +# if ((which.max(chi$residual)==4) || (which.min(chi$residual)==2)) res2<-res2+1 +# } else if (!((which.max(chi$residual)==2) || (which.max(chi$residual)==4) || (which.min(chi$residual)==2) || (which.min(chi$residual)==4)) & chi$statistic>=seuil) { +# print('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA') +# print('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA') +# } +# if (length(unique(as.vector(chi$residual)))==2) { +# nbexe<-nbexe+1 +# } + +# print('avec correction') +# if (min(tab)<5) chi<-chisq.test(tab,correct=TRUE) +# print(chi$statistic) +# #if (chi$statistic >=seuil) nbseuil<-nbseuil+1 +# print(chi$expected) +# print(chi$residuals) +# chit<-((abs(tab-chi$expected)-0.5)^2)/chi$expected +# print(chit) +# print('#####################') + } +# pp('NN1',NN1) +# pp('NN2',NN2) +# pp('maxchip',maxchip) +# pp('nbzeroun',nbzeroun) +# pp('res1',res1) +# pp('res2',res2) +# pp('nbseuil',nbseuil) +# pp('nbexe',nbexe) +# pp('nbcontrib', nbcontrib) + print('resultats elim item') + pp(clnb+1,length(listcol[[clnb+1]])) + pp(clnb,length(listcol[[clnb]])) +# pp('inf seuil',sominf) +# pp('nv',nv) + pp('ncclnb',ncclnb) + pp('ncclnbp',ncclnbp) +# pp('nz',nz) + #sink() + #lignes concernees + listrownamedtable<-rownames(dtable) + listrownamedtable<-as.integer(listrownamedtable) + newcol<-vector(length=nrow(dataori)) + #remplissage de la nouvelle colonne avec les nouvelles classes + print('remplissage') + num<-0 + for (ligne in listrownamedtable) { + num<-num+1 + newcol[ligne]<-as.integer(as.vector(dtable[,'cl'][num])[1]) + } + #recuperation de la classe precedante pour les cases vides + print('recuperation classes precedentes') + matori<-as.matrix(dataori) + if (i!=1) { + # options(warn=-1) + for (ligne in 1:length(newcol)) { + # print(newcol[ligne]) + if (newcol[ligne]==0) { # ce test renvoie un warning + if (ligne %in% rowelim){ + newcol[ligne]<-0 + } else { + newcol[ligne]<-matori[ligne,length(matori[1,])] + } + + } + } + # options(warn=0) + } + dataori<-cbind(dataori,newcol) + + tailleclasse<-as.matrix(summary(as.factor(as.character(dataori[,ncol(dataori)])))) + #tailleclasse<-as.integer(tailleclasse) + print('tailleclasse') + print(tailleclasse) + tailleclasse<-as.matrix(tailleclasse[!(rownames(tailleclasse)==0),]) + plusgrand<-which.max(tailleclasse) + #??????????????????????????????????? + #Si 2 classes ont des effectifs egaux, on prend la premiere de la liste... + if (length(plusgrand)>1) { + plusgrand<-plusgrand[1] + } + #???????????????????????????????????? + + #constuction du prochain tableau a analyser + print('construction tableau suivant') + classe<-as.integer(rownames(tailleclasse)[plusgrand]) + dtable<-dataori[dataori[,ncol(dataori)]==classe,] + row.names(dtable)<-rownames(dataori[dataori[,ncol(dataori)]==classe,]) + dtable<-dtable[,1:(ncol(dtable)-i)] + mere<-classe + listcolelim<-listcol[[as.integer(classe)]] + mother<-listmere[[as.integer(classe)]] + while (mother!=1) { + listcolelim<-append(listcolelim,listcol[[mother]]) + mother<-listmere[[mother]] + } + + listcolelim<-sort(unique(listcolelim)) + pp('avant',ncol(dtable)) + if (!is.logical(listcolelim)){ + print('elimination colonne') + #dtable<-dtable[,-listcolelim] + dtable<-dtable[,!(colnames(dtable) %in% listcolelim)] + } + pp('apres',ncol(dtable)) + #elimination des colonnes ne contenant que des 0 + print('vire colonne inf 3 dans boucle') + sdt<-colSums(dtable) + if (min(sdt)<=3) + dtable<-dtable[,-which(sdt<=3)] + + #elimination des lignes ne contenant que des 0 + print('vire ligne vide dans boucle') + if (ncol(dtable)==1) { + sdt<-dtable[,1] + } else { + sdt<-rowSums(dtable) + } + if (min(sdt)==0) { + rowelim<-as.integer(rownames(dtable)[which(sdt==0)]) + print('&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&') + print(rowelim) + print('&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&') + dtable<-dtable[-which(sdt==0),] + } +# } + } +# sink() + res <- list(n1 = dataori[,(ncol(dataori)-x+1):ncol(dataori)], list_mere = listmere, list_fille = list_fille) + res +} + +#dataout<-CHD(data,9) + +#igraph pour graph de relation diff --git a/Rscripts/CHDPOND.R b/Rscripts/CHDPOND.R new file mode 100644 index 0000000..a1a3ea6 --- /dev/null +++ b/Rscripts/CHDPOND.R @@ -0,0 +1,455 @@ +#Author: Pierre Ratinaud +#Copyright (c) 2008 Pierre Ratinaud +#Lisense: GNU/GPL + +#library(ca) +#library(MASS) +#source('/home/pierre/workspace/iramuteq/Rscripts/afc.R') +#data<-read.table('output/corpus_bin.csv',header=TRUE,sep='\t') +#source('/home/pierre/workspace/iramuteq/Rscripts/anacor.R') +#library(ade4) +pp<-function(txt,val) { + d<-paste(txt,' : ') + print(paste(d,val)) +} +MyChiSq<-function(x,sc,n){ + sr<-rowSums(x) + E <- outer(sr, sc, "*")/n + STAT<-sum((abs(x - E))^2/E) + STAT +} + +CHD<-function(data,x=9){ +# sink('/home/pierre/workspace/iramuteq/dev/findchi2.txt') + dataori=as.matrix(data) + row.names(dataori)=rownames(data) + dtable=as.matrix(data) + row.names(dtable)=rownames(data) + rowelim<-NULL + pp('ncol entree : ',ncol(dtable)) + pp('nrow entree',nrow(dtable)) + listcol <- list() + listmere <- list() + list_fille <- list() + print('vire colonnes vides en entree')#FIXME : il ne doit pas y avoir de colonnes vides en entree !! + sdt<-colSums(dtable) + if (min(sdt)==0) + dtable<-dtable[,-which(sdt==0)] + mere<-1 + for (i in 1:x) { + clnb<-(i*2) + listmere[[clnb]]<-mere + listmere[[clnb+1]]<-mere + list_fille[[mere]] <- c(clnb,clnb+1) + listcol[[clnb]]<-vector() + listcol[[clnb+1]]<-vector() + #extraction du premier facteur de l'afc + print('afc') + #afc<-ca(dtable,nd=1) + #afc<-corresp(dtable,nd=1) + #afc<-fca(dtable) + pp('taille dtable dans boucle (col/row)',c(ncol(dtable),nrow(dtable))) + afc<-boostana(dtable,nd=1) + #afc<-dudi.coa(dtable,scannf=FALSE,nf=1) + pp('SV',afc$singular.values) + pp('V.P.', afc$eigen.values) + #pp('V.P.',afc$eig[1]) + #pp('V.P.',afc$factexpl[1]) + #print(afc$chisq) + afcchi<-chisq.test(dtable) + Tinert<-afcchi$statistic/sum(dtable) + pp('inertie totale',Tinert) + #coordonnees des colonnes sur le premier facteur + #coordrow=afc$rowcoord + coordrow=as.matrix(afc$row.scores) + #coordrow<-as.matrix(afc$rproj[,1]) + #coordrow<-as.matrix(afc$li) + coordrowori<-coordrow + #row.names(coordrow)<-afc$rownames + row.names(coordrow)<-rownames(dtable) + #classement en fonction de la position sur le premier facteur + print('deb recherche meilleur partition') + ordertable<-cbind(dtable,coordrow) + coordrow<-as.matrix(coordrow[order(coordrow[,1]),]) + ordertable<-ordertable[order(ordertable[,ncol(ordertable)]),][,-ncol(ordertable)] + listinter<-vector() + listlim<-vector() + TT=sum(dtable) + sc<-colSums(dtable) + sc1<-ordertable[1,] + sc2<-colSums(dtable)-ordertable[1,] + chitable<-rbind(sc1,sc2) + #listinter<-vector() + maxinter<-0 + rmax<-NULL +################################################################# + for (l in 2:(nrow(dtable)-2)){ + chitable[1,]<-chitable[1,]+ordertable[l,] + chitable[2,]<-chitable[2,]-ordertable[l,] +# sc1<-sc1+ordertable[l,] +# sc2<-sc2-ordertable[l,] +# chitable<-rbind(sc1,sc2) + chi<-MyChiSq(chitable,sc,TT) + if (chi>maxinter) { + maxinter<-chi + rmax<-l + } + # listinter<-append(listinter,MyChiSq(chitable,sc,TT))#,chisq.test(chitable)$statistic/TT) + } + #plot(listinter) + print('@@@@@@@@@@@@@@@@@@@@@@@@@@@@') + pp('max inter phase 1', maxinter/TT)#max(listinter)) + print('@@@@@@@@@@@@@@@@@@@@@@@@@@@@') + #maxinter<-which.max(listinter)+1 + + listclasse<-ifelse(coordrowori<=coordrow[(rmax),1],clnb,clnb+1) + + cl<-listclasse + + pp('TT',TT) + #dtable<-cbind(dtable,'cl'= as.vector(cl)) + + N1<-length(listclasse[listclasse==clnb]) + N2<-length(listclasse[listclasse==clnb+1]) + pp('N1',N1) + pp('N2',N2) +################################################################### +# reclassement des individus # +################################################################### + malcl<-1000000000000 + it<-0 + listsub<-list() + #in boucle + + while (malcl!=0 & N1>=5 & N2>=5) { + it<-it+1 + listsub[[it]]<-vector() + pp('nombre iteration',it) + vdelta<-vector() + #dtable[,'cl']<-cl + t1<-dtable[cl==clnb,]#[,-ncol(dtable)] + t2<-dtable[cl==clnb+1,]#[,-ncol(dtable)] + ncolt<-ncol(t1) + pp('ncolt',ncolt) + sc1<-colSums(t1) + sc2<-colSums(t2) + sc<-sc1+sc2 + TT<-sum(dtable) + chtableori<-rbind(sc1,sc2) + interori<-MyChiSq(chtableori,sc,TT)/TT#chisq.test(chtableori)$statistic#/TT + chtable<-chtableori + pp('interori',interori) + N1<-nrow(t1) + N2<-nrow(t2) + pp('N1',N1) + pp('N2',N2) + + for (l in 1:nrow(dtable)){ + if(cl[l]==clnb){ + chtable[1,]<-sc1-dtable[l,] + chtable[2,]<-sc2+dtable[l,] + }else{ + chtable[1,]<-sc1+dtable[l,] + chtable[2,]<-sc2-dtable[l,] + } + interswitch<-MyChiSq(chtable,sc,TT)/TT#chisq.test(chtable)$statistic/TT + ws<-interori-interswitch + + if (ws<0){ + interori<-interswitch + if(cl[l]==clnb){ + sc1<-chtable[1,] + sc2<-chtable[2,] + cl[l]<-clnb+1 + listsub[[it]]<-append(listsub[[it]],l) + }else{ + sc1<-chtable[1,] + sc2<-chtable[2,] + cl[l]<-clnb + listsub[[it]]<-append(listsub[[it]],l) + } + vdelta<-append(vdelta,l) + } + } +# for (val in vdelta) { +# if (cl[val]==clnb) { +# cl[val]<-clnb+1 +# listsub[[it]]<-append(listsub[[it]],val) +# }else { +# cl[val]<-clnb +# listsub[[it]]<-append(listsub[[it]],val) +# } +# } + print('###################################') + print('longueur < 0') + malcl<-length(vdelta) + if ((it>1)&&(!is.logical(listsub[[it]]))){ + if (listsub[[it]]==listsub[[(it-1)]]){ + malcl<-0 + } + } + print(malcl) + print('###################################') + } + dtable<-cbind(dtable,'cl'=as.vector(cl)) +####################################################################### +# Fin reclassement des individus # +####################################################################### +# if (!(length(cl[cl==clnb])==1 || length(cl[cl==clnb+1])==1)) { + t1<-dtable[dtable[,'cl']==clnb,][,-ncol(dtable)] + t2<-dtable[dtable[,'cl']==clnb+1,][,-ncol(dtable)] + + chtable<-rbind(colSums(t1),colSums(t2)) + inter<-chisq.test(chtable)$statistic/TT + pp('last inter',inter) + print('=====================') + + #calcul de la specificite des colonnes + mint<-min(nrow(t1),nrow(t2)) + maxt<-max(nrow(t1),nrow(t2)) + seuil<-round((1.9*(maxt/mint))+1.9,digit=6) + #sink('/home/pierre/workspace/iramuteq/dev/findchi2.txt') +# print('ATTENTION SEUIL 3,84') +# seuil<-3.84 + pp('seuil',seuil) + sominf<-0 + nv<-0 + nz<-0 + ncclnb<-0 + ncclnbp<-0 + NN1<-0 + NN2<-0 + maxchip<-0 + nbzeroun<-0 + res1<-0 + res2<-0 + nbseuil<-0 + nbexe<-0 + nbcontrib<-0 + cn<-colnames(dtable)[1:(ncol(dtable)-1)] +# for (k in 1:(ncol(dtable)-1)) { +# #print(k) +# tab<-table(dtable[,k],dtable[,ncol(dtable)]) +# #print(cn[k]) +# #print (tab) +# #chidemoi<-(sum(t)*(((t[1,1]*t[2,2])-(t[1,2]*t[2,1]))^2))/((rowSums(t)[1])*(rowSums(t)[2])*(colSums(t)[1])*(colSums(t)[2])) +# if (rownames(tab)[1]==1 & nrow(tab)==1) { +# tab<-rbind(c(0,0),tab[1]) +# print('tableau vide dessus') +# } +# if (min(tab)<=10){ +# chi<-chisq.test(tab,correct=TRUE) +# }else{ +# chi<-chisq.test(tab,correct=FALSE) +# } +# if ((min(tab[2,])==0) & (min(tab[1,])!=0)){ +# chi$statistic<-seuil+1 +# # print('min tab 2 == 0') +# } +## if(((tab[2,1]>tab[1,1]) & (tab[2,2]>tab[1,2]))){ +## nz<-nz+1 +## chi$statistic<-seuil-1 +## sominf<-sominf-1 +## } +# if ((min(tab[1,])==0) & (min(tab[2,])!=0)){ +# chi$statistic<-seuil-1 +# # print('min tab 1 == 0') +# } +# if (is.na(chi$statistic)) { +# chi$statistic<-0 +# print('NA dans chi$statistic') +# } +## print('#####################') +# if (chi$statistic>seuil){ +# i# print('sup seuil') +# if (tab[2,1]=seuil) { +## if (which.max(chit)==2) NN1<-NN1+1 +## if (which.max(chit)==4) NN2<-NN2+1 +## } +## if (max(abs(contrib[2,])>=1.96)) { +## nbcontrib<-nbcontrib+1 +## } +## if (max(chit[2,]>=seuil)) maxchip<-maxchip+1 +## if (chi$statistic >=seuil) nbseuil<-nbseuil+1 +## if ((min(tab[2,])==0) & (chi$statistic=seuil) { +## if ((which.max(chi$residual)==2) || (which.min(chi$residual)==4)) res1<-res1+1 +## if ((which.max(chi$residual)==4) || (which.min(chi$residual)==2)) res2<-res2+1 +## } else if (!((which.max(chi$residual)==2) || (which.max(chi$residual)==4) || (which.min(chi$residual)==2) || (which.min(chi$residual)==4)) & chi$statistic>=seuil) { +## print('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA') +## print('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA') +## } +## if (length(unique(as.vector(chi$residual)))==2) { +## nbexe<-nbexe+1 +## } +# +## print('avec correction') +## if (min(tab)<5) chi<-chisq.test(tab,correct=TRUE) +## print(chi$statistic) +## #if (chi$statistic >=seuil) nbseuil<-nbseuil+1 +## print(chi$expected) +## print(chi$residuals) +## chit<-((abs(tab-chi$expected)-0.5)^2)/chi$expected +## print(chit) +## print('#####################') +# } +## pp('NN1',NN1) +## pp('NN2',NN2) +## pp('maxchip',maxchip) +## pp('nbzeroun',nbzeroun) +## pp('res1',res1) +## pp('res2',res2) +## pp('nbseuil',nbseuil) +## pp('nbexe',nbexe) +## pp('nbcontrib', nbcontrib) + print('resultats elim item') + pp(clnb+1,length(listcol[[clnb+1]])) + pp(clnb,length(listcol[[clnb]])) +# pp('inf seuil',sominf) +# pp('nv',nv) + pp('ncclnb',ncclnb) + pp('ncclnbp',ncclnbp) +# pp('nz',nz) + #sink() + #lignes concernees + listrownamedtable<-rownames(dtable) + listrownamedtable<-as.integer(listrownamedtable) + newcol<-vector(length=nrow(dataori)) + #remplissage de la nouvelle colonne avec les nouvelles classes + print('remplissage') + num<-0 + for (ligne in listrownamedtable) { + num<-num+1 + newcol[ligne]<-as.integer(as.vector(dtable[,'cl'][num])[1]) + } + #recuperation de la classe precedante pour les cases vides + print('recuperation classes precedentes') + matori<-as.matrix(dataori) + if (i!=1) { + # options(warn=-1) + for (ligne in 1:length(newcol)) { + # print(newcol[ligne]) + if (newcol[ligne]==0) { # ce test renvoie un warning + if (ligne %in% rowelim){ + newcol[ligne]<-0 + } else { + newcol[ligne]<-matori[ligne,length(matori[1,])] + } + + } + } + # options(warn=0) + } + dataori<-cbind(dataori,newcol) + + tailleclasse<-as.matrix(summary(as.factor(as.character(dataori[,ncol(dataori)])))) + #tailleclasse<-as.integer(tailleclasse) + print('tailleclasse') + print(tailleclasse) + tailleclasse<-as.matrix(tailleclasse[!(rownames(tailleclasse)==0),]) + plusgrand<-which.max(tailleclasse) + #??????????????????????????????????? + #Si 2 classes ont des effectifs egaux, on prend la premiere de la liste... + if (length(plusgrand)>1) { + plusgrand<-plusgrand[1] + } + #???????????????????????????????????? + + #constuction du prochain tableau a analyser + print('construction tableau suivant') + classe<-as.integer(rownames(tailleclasse)[plusgrand]) + dtable<-dataori[dataori[,ncol(dataori)]==classe,] + row.names(dtable)<-rownames(dataori[dataori[,ncol(dataori)]==classe,]) + dtable<-dtable[,1:(ncol(dtable)-i)] + mere<-classe + listcolelim<-listcol[[as.integer(classe)]] + mother<-listmere[[as.integer(classe)]] + while (mother!=1) { + listcolelim<-append(listcolelim,listcol[[mother]]) + mother<-listmere[[mother]] + } + + listcolelim<-sort(unique(listcolelim)) + pp('avant',ncol(dtable)) + if (!is.logical(listcolelim)){ + print('elimination colonne') + #dtable<-dtable[,-listcolelim] + dtable<-dtable[,!(colnames(dtable) %in% listcolelim)] + } + pp('apres',ncol(dtable)) + #elimination des colonnes ne contenant que des 0 + print('vire colonne inf 3 dans boucle') + sdt<-colSums(dtable) + if (min(sdt)<=3) + dtable<-dtable[,-which(sdt<=3)] + + #elimination des lignes ne contenant que des 0 + print('vire ligne vide dans boucle') + if (ncol(dtable)==1) { + sdt<-dtable[,1] + } else { + sdt<-rowSums(dtable) + } + if (min(sdt)==0) { + rowelim<-as.integer(rownames(dtable)[which(sdt==0)]) + print('&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&') + print(rowelim) + print('&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&') + dtable<-dtable[-which(sdt==0),] + } +# } + } +# sink() + res <- list(n1 = dataori[,(ncol(dataori)-x+1):ncol(dataori)], list_mere = listmere, list_fille = list_fille) + res +} diff --git a/Rscripts/Rfunct.R b/Rscripts/Rfunct.R new file mode 100644 index 0000000..2764ce9 --- /dev/null +++ b/Rscripts/Rfunct.R @@ -0,0 +1,51 @@ +ColType <- function(x) { +#x data.frame + type<-character() + for (i in 1:ncol(x)) { + tmp<-as.matrix(x[,i]) + type[i]<-mode(tmp) + } + type +} + +LevelsNb<- function(x) { + levelsnb<-numeric() + for (i in 1:ncol(x)) { + if (mode(as.matrix(x[,i]))=="character") { + levelsnb[i]<-length(levels(x[,i])) + } + else { + levelsnb[i]<-NA + } + } + levelsnb +} + +ReadData<- function(filename,sep=';',quote='"', header=TRUE, encoding='', na.strings ='',rownames=NULL) { + if (version$os=='linux-gnu') { + dataimport<-read.csv2(file(filename,encoding=encoding), header=header, sep=sep, quote=quote, na.strings='',row.names=rownames) + } else { + dataimport<-read.csv2(filename,encoding=encoding, header=header,sep=sep, quote=quote,na.strings='',row.names=rownames) + } + dataimport +} + +testzero<-function(mydata) { + nc<-vector() + nl<-vector() + for (i in 1:ncol(mydata)) { + if (sum(mydata[,i])==0) { + print('zero') + nc<-append(nc,i) + } + } + print('suite') + for (j in 1:nrow(mydata)) { + print(j) + if (sum(mydata[j,])==0) { + print('zero') + nl<-append(nl,j) + } + } + c(nc,nl) +} diff --git a/Rscripts/Rgraph.R b/Rscripts/Rgraph.R new file mode 100644 index 0000000..4759448 --- /dev/null +++ b/Rscripts/Rgraph.R @@ -0,0 +1,575 @@ +############FIXME################## +PlotDendroComp <- function(chd,filename,reso) { + jpeg(filename,res=reso) + par(cex=PARCEX) + plot(chd,which.plots=2, hang=-1) + dev.off() +} + +PlotDendroHori <- function(dendrocutupper,filename,reso) { + jpeg(filename,res=reso) + par(cex=PARCEX) + nP <- list(col=3:2, cex=c(0.5, 0.75), pch= 21:22, bg= c('light blue', 'pink'),lab.cex = 0.75, lab.col = 'tomato') + plot(dendrocutupper,nodePar= nP, edgePar = list(col='gray', lwd=2),horiz=TRUE, center=FALSE) + dev.off() +} + +PlotDendroCut <- function(chd,filename,reso,clusternb) { + h.chd <- as.hclust(chd) + memb <- cutree(h.chd, k = clusternb) + cent <- NULL + for(k in 1:clusternb){ + cent <- rbind(cent, k) + } + h.chd1 <- hclust(dist(cent)^2, method = 'cen', members = table(memb)) + h.chd1$labels <- sprintf('CL %02d',1:clusternb) + nP <- list(col=3:2, cex=c(2.0, 0.75), pch= 21:22, bg= c('light blue', 'pink'),lab.cex = 0.75, lab.col = 'tomato') + jpeg(filename,res=reso) + par(cex=PARCEX) + plot(h.chd1, nodePar= nP, edgePar = list(col='gray', lwd=2), horiz=TRUE, center=TRUE, hang= -1) + dev.off() +} + +PlotAfc<- function(afc, filename, width=800, height=800, quality=100, reso=200, toplot=c('all','all'), PARCEX=PARCEX) { + if (Sys.info()["sysname"]=='Darwin') { + width<-width/74.97 + height<-height/74.97 + quartz(file=filename,type='jpeg',width=width,height=height) + } else { + jpeg(filename,width=width,height=height,quality=quality,res=reso) + } + par(cex=PARCEX) + plot(afc,what=toplot,labels=c(1,1),contrib=c('absolute','relative')) + dev.off() +} + +PlotAfc2dCoul<- function(afc,chisqrtable,filename, what='coord',col=FALSE, axetoplot=c(1,2), deb=0,fin=0, width=800, height=800, quality=100, reso=200, parcex=PARCEX, xlab = NULL, ylab = NULL) { + if (col) { + if (what == 'coord') { + rowcoord <- as.matrix(afc$colcoord) + } else { + rowcoord <- as.matrix(afc$colcrl) + } + } else { + if (what == 'coord') { + rowcoord <- as.matrix(afc$rowcoord) + } else { + rowcoord <- as.matrix(afc$rowcrl) + } + } + x <- axetoplot[1] + y <- axetoplot[2] + + if (col) + rownames(rowcoord) <- afc$colnames + if (!col){ + rownames(rowcoord) <- afc$rownames + rowcoord <- as.matrix(rowcoord[deb:fin,]) + chitable<- as.matrix(chisqrtable[deb:fin,]) + #row_keep <- select_point_nb(chitable,15) + } + if (ncol(rowcoord) == 1) { + rowcoord <- t(rowcoord) + } + clnb <- ncol(chisqrtable) + + if (!col) classes <- as.matrix(apply(chitable,1,which.max)) + else classes <- 1:clnb + ntabtot <- cbind(rowcoord, classes) + #if (!col) ntabtot <- ntabtot[row_keep,] + open_file_graph(filename, width = width, height = height) + par(cex=PARCEX) + plot(rowcoord[,x],rowcoord[,y], pch='', xlab = xlab, ylab = ylab) + abline(h=0,v=0) + for (i in 1:clnb) { + ntab <- subset(ntabtot,ntabtot[,ncol(ntabtot)] == i) + if (nrow(ntab) != 0) + text(ntab[,x],ntab[,y],rownames(ntab),col=rainbow(clnb)[i]) + } + dev.off() +} + +filename.to.svg <- function(filename) { + filename <- gsub('.png', '.svg', filename) + return(filename) +} + +open_file_graph <- function (filename, width=800, height = 800, quality = 100, svg = FALSE) { + if (Sys.info()["sysname"] == 'Darwin') { + width <- width/74.97 + height <- height/74.97 + quartz(file = filename, type = 'jpeg', width = width, height = height) + } else { + #print('ATTENTION SVG!!!!!!!!!!!!!!!!!!!!!!!!!!!') + #library(RSvgDevice) + if (svg) { + svg(filename.to.svg(filename), width=width/74.97, height=height/74.97) + } else { + png(filename, width=width, height=height)#, quality = quality) + } + } +} + +make_tree_tot <- function (chd) { + library(ape) + lf<-chd$list_fille + clus<-'a1a;' + for (i in 1:length(lf)) { + if (!is.null(lf[[i]])) { + clus<-gsub(paste('a',i,'a',sep=''),paste('(','a',lf[[i]][1],'a',',','a',lf[[i]][2],'a',')',sep=''),clus) + } + } + dendro_tuple <- clus + clus <- gsub('a','',clus) + tree.cl <- read.tree(text = clus) + res<-list(tree.cl = tree.cl, dendro_tuple = dendro_tuple) + res +} + +make_dendro_cut_tuple <- function(dendro_in, coordok, classeuce, x, nbt = 9) { + library(ape) + dendro<-dendro_in + i <- 0 + for (cl in coordok[,x]) { + i <- i + 1 + fcl<-fille(cl,classeuce) + for (fi in fcl) { + dendro <- gsub(paste('a',fi,'a',sep=''),paste('b',i,'b',sep=''),dendro) + } + } + clnb <- nrow(coordok) + tcl=((nbt+1) *2) - 2 + for (i in 1:(tcl + 1)) { + dendro <- gsub(paste('a',i,'a',sep=''),paste('b',0,'b',sep=''),dendro) + } + dendro <- gsub('b','',dendro) + dendro <- gsub('a','',dendro) + dendro_tot_cl <- read.tree(text = dendro) + #FIXME + for (i in 1:10) { + for (cl in 1:clnb) { + dendro <- gsub(paste('\\(',cl,',',cl,'\\)',sep=''),cl,dendro) + } + } + for (i in 1:10) { + dendro <- gsub(paste('\\(',0,',',0,'\\)',sep=''),0,dendro) + for (cl in 1:clnb) { + dendro <- gsub(paste('\\(',0,',',cl,'\\)',sep=''),cl,dendro) + dendro <- gsub(paste('\\(',cl,',',0,'\\)',sep=''),cl,dendro) + } + } + print(dendro) + tree.cl <- read.tree(text = dendro) + lab <- tree.cl$tip.label + if ("0" %in% lab) { + tovire <- which(lab == "0") + tree.cl <- drop.tip(tree.cl, tip = tovire) + } + res <- list(tree.cl = tree.cl, dendro_tuple_cut = dendro, dendro_tot_cl = dendro_tot_cl) + res +} + +select_point_nb <- function(tablechi, nb) { + chimax<-as.matrix(apply(tablechi,1,max)) + chimax<-cbind(chimax,1:nrow(tablechi)) + order_chi<-as.matrix(chimax[order(chimax[,1],decreasing = TRUE),]) + row_keep <- order_chi[,2][1:nb] + row_keep +} + +select_point_chi <- function(tablechi, chi_limit) { + chimax<-as.matrix(apply(tablechi,1,max)) + row_keep <- which(chimax >= chi_limit) + row_keep +} + +#from summary.ca +summary.ca.dm <- function(object, scree = TRUE, ...){ + obj <- object + nd <- obj$nd + if (is.na(nd)){ + nd <- 2 + } else { + if (nd > length(obj$sv)) nd <- length(obj$sv) + } + # principal coordinates: + K <- nd + I <- dim(obj$rowcoord)[1] ; J <- dim(obj$colcoord)[1] + svF <- matrix(rep(obj$sv[1:K], I), I, K, byrow = TRUE) + svG <- matrix(rep(obj$sv[1:K], J), J, K, byrow = TRUE) + rpc <- obj$rowcoord[,1:K] * svF + cpc <- obj$colcoord[,1:K] * svG + + # rows: + r.names <- obj$rownames + sr <- obj$rowsup + if (!is.na(sr[1])) r.names[sr] <- paste("(*)", r.names[sr], sep = "") + r.mass <- obj$rowmass + r.inr <- obj$rowinertia / sum(obj$rowinertia, na.rm = TRUE) + r.COR <- matrix(NA, nrow = length(r.names), ncol = nd) + colnames(r.COR) <- paste('COR -facteur', 1:nd, sep=' ') + r.CTR <- matrix(NA, nrow = length(r.names), ncol = nd) + colnames(r.CTR) <- paste('CTR -facteur', 1:nd, sep=' ') + for (i in 1:nd){ + r.COR[,i] <- obj$rowmass * rpc[,i]^2 / obj$rowinertia + r.CTR[,i] <- obj$rowmass * rpc[,i]^2 / obj$sv[i]^2 + } + # cor and quality for supplementary rows + if (length(obj$rowsup) > 0){ + i0 <- obj$rowsup + for (i in 1:nd){ + r.COR[i0,i] <- obj$rowmass[i0] * rpc[i0,i]^2 + r.CTR[i0,i] <- NA + } + } + + # columns: + c.names <- obj$colnames + sc <- obj$colsup + if (!is.na(sc[1])) c.names[sc] <- paste("(*)", c.names[sc], sep = "") + c.mass <- obj$colmass + c.inr <- obj$colinertia / sum(obj$colinertia, na.rm = TRUE) + c.COR <- matrix(NA, nrow = length(c.names), ncol = nd) + colnames(c.COR) <- paste('COR -facteur', 1:nd, sep=' ') + c.CTR <- matrix(NA, nrow = length(c.names), ncol = nd) + colnames(c.CTR) <- paste('CTR -facteur', 1:nd, sep=' ') + for (i in 1:nd) + { + c.COR[,i] <- obj$colmass * cpc[,i]^2 / obj$colinertia + c.CTR[,i] <- obj$colmass * cpc[,i]^2 / obj$sv[i]^2 + } + if (length(obj$colsup) > 0){ + i0 <- obj$colsup + for (i in 1:nd){ + c.COR[i0,i] <- obj$colmass[i0] * cpc[i0,i]^2 + c.CTR[i0,i] <- NA + } + } + + # scree plot: + if (scree) { + values <- obj$sv^2 + values2 <- 100*(obj$sv^2)/sum(obj$sv^2) + values3 <- cumsum(100*(obj$sv^2)/sum(obj$sv^2)) + scree.out <- cbind(values, values2, values3) + } else { + scree.out <- NA + } + + obj$r.COR <- r.COR + obj$r.CTR <- r.CTR + obj$c.COR <- c.COR + obj$c.CTR <- c.CTR + obj$facteur <- scree.out + return(obj) + } + +create_afc_table <- function(x) { + #x = afc + facteur.table <- as.matrix(x$facteur) + nd <- ncol(x$colcoord) + rownames(facteur.table) <- paste('facteur',1:nrow(facteur.table),sep = ' ') + colnames(facteur.table) <- c('Valeurs propres', 'Pourcentages', 'Pourcentage cumules') + ligne.table <- as.matrix(x$rowcoord) + rownames(ligne.table) <- x$rownames + colnames(ligne.table) <- paste('Coord. facteur', 1:nd, sep=' ') + tmp <- as.matrix(x$rowcrl) + colnames(tmp) <- paste('Corr. facteur', 1:nd, sep=' ') + ligne.table <- cbind(ligne.table,tmp) + ligne.table <- cbind(ligne.table, x$r.COR) + ligne.table <- cbind(ligne.table, x$r.CTR) + ligne.table <- cbind(ligne.table, mass = x$rowmass) + ligne.table <- cbind(ligne.table, chi.distance = x$rowdist) + ligne.table <- cbind(ligne.table, inertie = x$rowinertia) + colonne.table <- x$colcoord + rownames(colonne.table) <- paste('classe', 1:(nrow(colonne.table)),sep=' ') + colnames(colonne.table) <- paste('Coord. facteur', 1:nd, sep=' ') + tmp <- as.matrix(x$colcrl) + colnames(tmp) <- paste('Corr. facteur', 1:nd, sep=' ') + colonne.table <- cbind(colonne.table, tmp) + colonne.table <- cbind(colonne.table, x$c.COR) + colonne.table <- cbind(colonne.table, x$c.CTR) + colonne.table <- cbind(colonne.table, mass = x$colmass) + colonne.table <- cbind(colonne.table, chi.distance = x$coldist) + colonne.table <- cbind(colonne.table, inertie = x$colinertia) + res <- list(facteur = facteur.table, ligne = ligne.table, colonne = colonne.table) + res +} + +make_afc_graph <- function(toplot,classes,clnb, xlab, ylab, cex.txt = NULL, leg = FALSE, cmd = FALSE) { + rain <- rainbow(clnb) + compt <- 1 + tochange <- NULL + for (my.color in rain) { + my.color <- col2rgb(my.color) + if ((my.color[1] > 200) & (my.color[2] > 200) & (my.color[3] < 20)) { + tochange <- append(tochange, compt) + } + compt <- compt + 1 + } + if (!is.null(tochange)) { + gr.col <- grey.colors(length(tochange)) + compt <- 1 + for (val in tochange) { + rain[val] <- gr.col[compt] + compt <- compt + 1 + } + } + cl.color <- rain[classes] + plot(toplot[,1],toplot[,2], pch='', xlab = xlab, ylab = ylab) + abline(h=0,v=0, lty = 'dashed') + if (is.null(cex.txt)) + text(toplot[,1],toplot[,2],rownames(toplot),col=cl.color) + else + text(toplot[,1],toplot[,2],rownames(toplot),col=cl.color, cex = cex.txt) + + if (!cmd) { + dev.off() + } +} + +plot.dendropr <- function(tree, classes, type.dendro="phylogram", histo=FALSE, from.cmd=FALSE, bw=FALSE, lab = NULL, tclasse=TRUE) { + classes<-classes[classes!=0] + classes<-as.factor(classes) + sum.cl<-as.matrix(summary(classes)) + sum.cl<-(sum.cl/colSums(sum.cl)*100) + sum.cl<-round(sum.cl,2) + sum.cl<-cbind(sum.cl,as.matrix(100-sum.cl[,1])) + tree.order<- as.numeric(tree$tip.label) + if (! bw) { + col = rainbow(nrow(sum.cl))[as.numeric(tree$tip.label)] + col.bars <- col + col.pie <- rainbow(nrow(sum.cl)) + #col.vec<-rainbow(nrow(sum.cl))[as.numeric(tree[[2]])] + } else { + col = 'black' + col.bars = 'grey' + col.pie <- rep('grey',nrow(sum.cl)) + } + vec.mat<-NULL + for (i in 1:nrow(sum.cl)) vec.mat<-append(vec.mat,1) + v<-2 + for (i in 1:nrow(sum.cl)) { + vec.mat<-append(vec.mat,v) + v<-v+1 + } + par(mar=c(0,0,0,0)) + if (tclasse) { + if (! histo) { + layout(matrix(vec.mat,nrow(sum.cl),2),widths=c(3,1)) + } else { + layout(matrix(c(1,2),1,byrow=TRUE), widths=c(3,2),TRUE) + } + } + par(mar=c(0,0,0,0),cex=1) + label.ori<-tree[[2]] + if (!is.null(lab)) { + tree$tip.label <- lab + } else { + tree[[2]]<-paste('classe ',tree[[2]]) + } + plot.phylo(tree,label.offset=0.1,tip.col=col, type=type.dendro) + #cl.order <- as.numeric(label.ori) + #sum.cl[cl.order,1] + #for (i in 1:nrow(sum.cl)) { + if (tclasse) { + if (! histo) { + for (i in rev(tree.order)) { + par(mar=c(0,0,1,0),cex=0.7) + pie(sum.cl[i,],col=c(col.pie[i],'white'),radius = 1, labels='', clockwise=TRUE, main = paste('classe ',i,' - ',sum.cl[i,1],'%' )) + } + } else { + par(cex=0.7) + par(mar=c(0,0,0,1)) + to.plot <- sum.cl[tree.order,1] + d <- barplot(to.plot,horiz=TRUE, col=col.bars, names.arg='', axes=FALSE, axisname=FALSE) + text(x=to.plot, y=d[,1], label=paste(round(to.plot,1),'%'), adj=1.2) + } + } + if (!from.cmd) dev.off() + tree[[2]]<-label.ori +} +#tree <- tree.cut1$tree.cl +#to.plot <- di +plot.dendro.lex <- function(tree, to.plot, bw=FALSE, lab=NULL, lay.width=c(3,3,2), cmd=FALSE) { + tree.order<- as.numeric(tree$tip.label) + par(mar=c(0,0,0,0)) + layout(matrix(c(1,2,3),1,byrow=TRUE), widths=lay.width,TRUE) + par(mar=c(3,0,2,0),cex=1) + label.ori<-tree[[2]] + if (!is.null(lab)) { + tree$tip.label <- lab + } else { + tree[[2]]<-paste('classe ',tree[[2]]) + } + to.plot <- matrix(to.plot[,tree.order], nrow=nrow(to.plot), dimnames=list(rownames(to.plot), colnames(to.plot))) + if (!bw) { + col <- rainbow(ncol(to.plot)) + col.bars <- rainbow(nrow(to.plot)) + } else { + col <- 'black' + col.bars <- grey.colors(nrow(to.plot),0,0.8) + } + col <- col[tree.order] + plot.phylo(tree,label.offset=0.1,tip.col=col) + + par(mar=c(3,0,2,1)) + d <- barplot(to.plot,horiz=TRUE, col=col.bars, beside=TRUE, names.arg='', space = c(0.1,0.6), axisname=FALSE) + c <- colMeans(d) + c1 <- c[-1] + c2 <- c[-length(c)] + cc <- cbind(c1,c2) + lcoord <- apply(cc, 1, mean) + abline(h=lcoord) + if (min(to.plot) < 0) { + amp <- abs(max(to.plot) - min(to.plot)) + } else { + amp <- max(to.plot) + } + if (amp < 10) { + d <- 2 + } else { + d <- signif(amp%/%10,1) + } + mn <- round(min(to.plot)) + mx <- round(max(to.plot)) + for (i in mn:mx) { + if ((i/d) == (i%/%d)) { + abline(v=i,lty=3) + } + } + par(mar=c(0,0,0,0)) + plot(0, axes = FALSE, pch = '') + legend(x = 'center' , rownames(to.plot), fill = col.bars) + if (!cmd) { + dev.off() + } + tree[[2]]<-label.ori +} + +plot.alceste.graph <- function(rdata,nd=3,layout='fruke', chilim = 2) { + load(rdata) + if (is.null(debsup)) { + tab.toplot<-afctable[1:(debet+1),] + chitab<-chistabletot[1:(debet+1),] + } else { + tab.toplot<-afctable[1:(debsup+1),] + chitab<-chistabletot[1:(debsup+1),] + } + rkeep<-select_point_chi(chitab,chilim) + tab.toplot<-tab.toplot[rkeep,] + chitab<-chitab[rkeep,] + dm<-dist(tab.toplot,diag=TRUE,upper=TRUE) + cn<-rownames(tab.toplot) + cl.toplot<-apply(chitab,1,which.max) + col<-rainbow(ncol(tab.toplot))[cl.toplot] + library(igraph) + g1 <- graph.adjacency(as.matrix(dm), mode = 'lower', weighted = TRUE) + g.max<-minimum.spanning.tree(g1) + we<-(rowSums(tab.toplot)/max(rowSums(tab.toplot)))*2 + #lo <- layout.fruchterman.reingold(g.max,dim=nd) + lo<- layout.kamada.kawai(g.max,dim=nd) + print(nrow(tab.toplot)) + print(nrow(chitab)) + print(length(we)) + print(length(col)) + print(length(cn)) + if (nd == 3) { + rglplot(g.max, vertex.label = cn, vertex.size = we*3, edge.width = 0.5, edge.color='black', vertex.label.color = col,vertex.color = col, layout = lo, vertex.label.cex = 1) + } else if (nd == 2) { + plot(g.max, vertex.label = cn, vertex.size = we, edge.width = 0.5, edge.color='black', vertex.label.color = col,vertex.color = col, layout = lo, vertex.label.cex = 0.8) + } + +} + +make.simi.afc <- function(x,chitable,lim=0, alpha = 0.1, movie = NULL) { + library(igraph) + chimax<-as.matrix(apply(chitable,1,max)) + chimax<-as.matrix(chimax[,1][1:nrow(x)]) + chimax<-cbind(chimax,1:nrow(x)) + order_chi<-as.matrix(chimax[order(chimax[,1],decreasing = TRUE),]) + if ((lim == 0) || (lim>nrow(x))) lim <- nrow(x) + x<-x[order_chi[,2][1:lim],] + maxchi <- chimax[order_chi[,2][1:lim],1] + #------------------------------------------------------- + limit<-nrow(x) + distm<-dist(x,diag=TRUE) + distm<-as.matrix(distm) + g1<-graph.adjacency(distm,mode='lower',weighted=TRUE) + g1<-minimum.spanning.tree(g1) + lo<-layout.kamada.kawai(g1,dim=3) + lo <- layout.norm(lo, -3, 3, -3, 3, -3, 3) + mc<-rainbow(ncol(chistabletot)) + chitable<-chitable[order_chi[,2][1:lim],] + cc <- apply(chitable, 1, which.max) + cc<-mc[cc] + #mass<-(rowSums(x)/max(rowSums(x))) * 5 + maxchi<-norm.vec(maxchi, 0.03, 0.3) + rglplot(g1,vertex.label = vire.nonascii(rownames(x)),vertex.label.color='black',vertex.color=cc,vertex.size = 0.1, layout=lo, rescale=FALSE) + rgl.spheres(lo, col = cc, radius = maxchi, alpha = alpha) + rgl.bg(color = c('white','black')) + if (!is.null(movie)) { + require(tcltk) + ReturnVal <- tkmessageBox(title="RGL 3 D",message="Cliquez pour commencer le film",icon="info",type="ok") + + movie3d(spin3d(axis=c(0,1,0),rpm=6), movie = 'film_graph', frames = "tmpfilm", duration=10, clean=TRUE, top = TRUE, dir = movie) + ReturnVal <- tkmessageBox(title="RGL 3 D",message="Film fini !",icon="info",type="ok") + } + while (rgl.cur() != 0) + Sys.sleep(1) + +} + +# from igraph +norm.vec <- function(v, min, max) { + + vr <- range(v) + if (vr[1]==vr[2]) { + fac <- 1 + } else { + fac <- (max-min)/(vr[2]-vr[1]) + } + (v-vr[1]) * fac + min +} + + +vire.nonascii <- function(rnames) { + print('vire non ascii') + couple <- list(c('é','e'), + c('è','e'), + c('ê','e'), + c('ë','e'), + c('î','i'), + c('ï','i'), + c('ì','i'), + c('à','a'), + c('â','a'), + c('ä','a'), + c('á','a'), + c('ù','u'), + c('û','u'), + c('ü','u'), + c('ç','c'), + c('ò','o'), + c('ô','o'), + c('ö','o'), + c('ñ','n') + ) + + for (c in couple) { + rnames<-gsub(c[1],c[2], rnames) + } + rnames +} + + + +#par(mar=c(0,0,0,0)) +#layout(matrix(c(1,2),1,byrow=TRUE), widths=c(3,2),TRUE) +#par(mar=c(1,0,1,0), cex=1) +#plot.phylo(tree,label.offset=0.1) +#par(mar=c(0,0,0,1)) +#to.plot <- sum.cl[cl.order,1] +#d <- barplot(to.plot,horiz=TRUE, names.arg='', axes=FALSE, axisname=FALSE) +#text(x=to.plot, y=d[,1], label=round(to.plot,1), adj=1.2) + diff --git a/Rscripts/Rprof.out b/Rscripts/Rprof.out new file mode 100644 index 0000000..8c7566c --- /dev/null +++ b/Rscripts/Rprof.out @@ -0,0 +1 @@ +sample.interval=20000 diff --git a/Rscripts/afc.R b/Rscripts/afc.R new file mode 100644 index 0000000..1efcfcb --- /dev/null +++ b/Rscripts/afc.R @@ -0,0 +1,65 @@ +fca <- function(xtab) { + +# Correspondence analysis of principal table. List returned with +# projections, correlations, and contributions of rows (observations), +# and columns (attributes). Eigenvalues are output to display device. +# FM, 2003/12. + + tot <- sum(xtab) + fIJ <- xtab/tot + fI <- apply(fIJ, 1, sum) + fJ <- apply(fIJ, 2, sum) + fJsupI <- sweep(fIJ, 1, fI, FUN="/") + #fIsupJ <- sweep(fIJ, 2, fJ, FUN="/") + s <- as.matrix(t(fJsupI)) %*% as.matrix(fIJ) + s1 <- sweep(s, 1, sqrt(fJ), FUN="/") + s2 <- sweep(s1, 2, sqrt(fJ), FUN="/") +# In following s2 is symmetric. However due to precision S-Plus didn't +# find it to be symmetric. And function eigen in S-Plus uses a different +# normalization for the non-symmetric case (in the case of some data)! + sres <- eigen(s2,symmetric=T) + sres$values[sres$values < 1.0e-8] <- 0.0 + factexpl<-sres$values[-1] + #tot <- sum(sres$values[-1]) + #factexplp<-100*sres$values[-1]/tot +# Eigenvectors divided rowwise by sqrt(fJ): + evectors <- sweep(sres$vectors, 1, sqrt(fJ), FUN="/") + +# PROJECTIONS ON FACTORS OF ROWS AND COLUMNS + rproj <- as.matrix(fJsupI) %*% evectors + #temp <- as.matrix(s2) %*% sres$vectors +# Following divides rowwise by sqrt(fJ) and columnwise by sqrt(eigenvalues): +# Note: first column of cproj is trivially 1-valued. +# NOTE: VBESxFACTORS. READ PROJS WITH FACTORS 1,2,... FROM COLS 2,3,... + #cproj <- sweep(sweep(temp,1,sqrt(fJ),FUN="/"),2,sqrt(sres$values),FUN="/") + +# CONTRIBUTIONS TO FACTORS BY ROWS AND COLUMNS +# Contributions: mass times projection distance squared. + #temp <- sweep( rproj^2, 1, fI, FUN="*") +# Normalize such that sum of contributions for a factor equals 1. + #sumCtrF <- apply(temp, 2, sum) +# Note: Obs. x factors. Read cntrs. with factors 1,2,... from cols. 2,3,... + #rcntr <- sweep(temp, 2, sumCtrF, FUN="/") + #temp <- sweep( cproj^2, 1, fJ, FUN="*") + #sumCtrF <- apply(temp, 2, sum) +# Note: Vbs. x factors. Read cntrs. with factors 1,2,... from cols. 2,3,... + #ccntr <- sweep(temp, 2, sumCtrF, FUN="/") + +# CORRELATIONS WITH FACTORS BY ROWS AND COLUMNS +# dstsq(i) = sum_j 1/fj (fj^i - fj)^2 + #temp <- sweep(fJsupI, 2, fJ, "-") + #dstsq <- apply( sweep( temp^2, 2, fJ, "/"), 1, sum) +# NOTE: Obs. x factors. Read corrs. with factors 1,2,... from cols. 2,3,... + #rcorr <- sweep(rproj^2, 1, dstsq, FUN="/") + #temp <- sweep(fIsupJ, 1, fI, "-") + #dstsq <- apply( sweep( temp^2, 1, fI, "/"), 2, sum) +# NOTE: Vbs. x factors. Read corrs. with factors 1,2,... from cols. 2,3,... + #ccorr <- sweep(cproj^2, 1, dstsq, "/") + +# Value of this function on return: list containing projections, correlations, +# and contributions for rows (observations), and for columns (variables). +# In all cases, allow for first trivial first eigenvector. + list(rproj=rproj[,-1],factexpl=factexpl)#,rcorr=rcorr[,-1], rcntr=rcntr[,-1],factexpl=factexpl,factexplp=factexplp, + #cproj=cproj[,-1], ccorr=ccorr[,-1], ccntr=ccntr[,-1]) + +} \ No newline at end of file diff --git a/Rscripts/afc_graph.R b/Rscripts/afc_graph.R new file mode 100644 index 0000000..17f065e --- /dev/null +++ b/Rscripts/afc_graph.R @@ -0,0 +1,223 @@ +#Author: Pierre Ratinaud +#Copyright (c) 20010 Pierre Ratinaud +#Lisense: GNU/GPL + + +#fichier genere par IRaMuTeq +source('%s') +typegraph <- %i +what <- %i +x <- %i +y <- %i +z <- %i +qui <- %i +over <- %s +do.select.nb <- %s +select.nb <- %i +do.select.chi <- %s +select.chi <- %i +cex.txt <- %s +txt.min <- %i +txt.max <- %i +fileout <- '%s' +width <- %i +height <- %i +taillecar <- %i +alpha <- %i/100 +dofilm <- %s +tchi <- %s +tchi.min <- %i +tchi.max <- %i +dirout <- '%s' + +xlab <- paste('facteur ', x, ' -') +ylab <- paste('facteur ', y, ' -') +if (!typegraph == 0) {zlab <- paste('facteur ', z, ' -')} +xlab <- paste(xlab,round(afc_table$facteur[x,2],2),sep = ' ') +xlab <- paste(xlab,' %%',sep = '') +ylab <- paste(ylab,round(afc_table$facteur[y,2],2),sep = ' ') +ylab <- paste(ylab,' %%',sep = '') +if (!typegraph == 0) { + zlab <- paste(zlab,round(afc_table$facteur[z,2],2),sep = ' ') + zlab <- paste(zlab,' %%',sep = '') +} + +if ( qui == 3 ) { + if ( what == 0 ) table.in <- afc$colcoord + if ( what == 1 ) table.in <- afc$colcrl + rownames(table.in) <- afc$colnames + if (typegraph == 0) { + table.in<-table.in[,c(x,y)] + } else { + table.in<-table.in[,c(x,y,z)] + rx <- range(table.in[,1], na.rm = TRUE) + ry <- range(table.in[,2], na.rm = TRUE) + rz <- range(table.in[,3], na.rm = TRUE) + } + classes <- c(1:clnb) + maxchi <- 1 + cex.par <- NULL +} else { + if ( what == 0 ) table.in <- afc$rowcoord + if ( what == 1 ) table.in <- afc$rowcrl*2 + rownames(table.in) <- afc$rownames + tablechi <- chistabletot + rn.keep <- c() + if (typegraph == 0) { + table.in<-table.in[,c(x,y)] + } else { + table.in<-table.in[,c(x,y,z)] + rx <- range(table.in[,1], na.rm = TRUE) + ry <- range(table.in[,2], na.rm = TRUE) + rz <- range(table.in[,3], na.rm = TRUE) + } + if (!is.null(debsup)) { + if ( qui == 0 ) { + table.in <- table.in[1:(debsup-1),] + tablechi <- tablechi[1:(debsup-1),] + cex.par <- afc$rowmass[1:(debsup-1)] + } + if ( qui == 1 ) { + table.in <- table.in[debsup:(debet-1),] + tablechi <- tablechi[debsup:(debet-1),] + #cex.par <- afc$rowmass[debsup:(debet-1)] + } + if ( qui == 2 ) { + table.in <- table.in[debet:nrow(table.in),] + tablechi <- tablechi[debet:nrow(tablechi),] + #cex.par <- afc$rowmass[debet:nrow(tablechi)] + } + } + + if (is.null(debsup)) { + if (qui == 0) { + if (!is.null(debet)) { + table.in <- table.in[1:(debet-1),] + tablechi <- tablechi[1:(debet-1),] + cex.par <- afc$rowmass[1:(debet-1)] + } else { + cex.par <- afc$rowmass + } + } else { + table.in <- table.in[debet:nrow(table.in),] + tablechi <- tablechi[debet:nrow(tablechi),] + #cex.par <- afc$rowmass[debet:nrow(tablechi)] + } + } + + if (over) { + rn <- rownames(table.in) + rownames(table.in) <- 1:nrow(table.in) + table.in <- unique(table.in) + rn.keep <- as.numeric(rownames(table.in)) + rownames(table.in) <- rn[rn.keep] + tablechi <- tablechi[rn.keep,] + if (qui==0) { + cex.par <- cex.par[rn.keep] + } else { + cex.par <- NULL + } + } + if (do.select.nb) { + if (select.nb > nrow(table.in)) select.nb <- nrow(table.in) + row.keep <- select_point_nb(tablechi, select.nb) + table.in <- table.in[row.keep,] + tablechi <- tablechi[row.keep,] + } else if (do.select.chi) { + if (select.chi > max(tablechi)) select.chi <- max(tablechi) + row.keep <- select_point_chi(tablechi, select.chi) + table.in <- table.in[row.keep,] + tablechi <- tablechi[row.keep,] + } else { + row.keep <- 1:nrow(table.in) + } + classes <- apply(tablechi, 1, which.max) + maxchi <- apply(tablechi, 1, max) + + if (cex.txt) { + #row.keep <- append(row.keep, rn.keep) + #row.keep <- unique(row.keep) + cex.par <- cex.par[row.keep] + cex.par <- norm.vec(cex.par, txt.min/10, txt.max/10) + } else if (tchi) { + cex.par <- maxchi + cex.par <- norm.vec(cex.par, tchi.min/10, tchi.max/10) + } else { + cex.par <- NULL + } +} + +if (typegraph == 0) { + + open_file_graph(fileout, width = width, height = height) + parcex <- taillecar/10 + par(cex = parcex) + make_afc_graph(table.in, classes, clnb, xlab, ylab, cex.txt = cex.par) + +} else { + + vire.nonascii <- function(rnames) { + print('vire non ascii') + couple <- list(c('é','e'), + c('è','e'), + c('ê','e'), + c('ë','e'), + c('î','i'), + c('ï','i'), + c('ì','i'), + c('à','a'), + c('â','a'), + c('ä','a'), + c('á','a'), + c('ù','u'), + c('û','u'), + c('ü','u'), + c('ç','c'), + c('ò','o'), + c('ô','o'), + c('ö','o'), + c('ñ','n') + ) + for (c in couple) { + rnames<-gsub(c[1],c[2], rnames) + } + rnames + } + library(rgl) + rn <- vire.nonascii(rownames(table.in)) + rgl.open() + #par3d(windowRect = c(100,100,600,600)) + rgl.bg(col = c('white', "#99bb99"), front = "lines", box=FALSE, sphere = TRUE) + rgl.lines(c(rx), c(0, 0), c(0, 0), col = "#000000") + rgl.lines(c(0,0),c(ry),c(0,0),col = "#000000") + rgl.lines(c(0,0),c(0,0),c(rz),col = "#000000") + text3d(rx[2]+1,0,0, xlab) + text3d(0,ry[2]+1,0, ylab) + text3d(0,0,rz[2]+1, zlab) + rain = rainbow(clnb) + colors = rain[classes] + text3d(table.in[,1], table.in[,2], table.in[,3], rn, col='black') + if (tchi) { + maxchi <- norm.vec(maxchi, tchi.min/100, tchi.max/100) + } else if (!is.null(cex.par)) { + maxchi <- norm.vec(cex.par, txt.min/100, txt.max/100) + } else { + maxchi <- 0.1 + } + for (i in 1:clnb) { + text3d(rx[2],(ry[2]+(0.2*i)),0,paste('classe',i),col=rain[i]) + } + rgl.spheres(table.in, col = colors, radius = maxchi, alpha = alpha) + + if (dofilm) { + require(tcltk) + ReturnVal <- tkmessageBox(title="RGL 3 D",message="Cliquez pour commencer le film",icon="info",type="ok") + + movie3d(spin3d(axis=c(0,1,0),rpm=6), movie = 'film', frames = "tmpfilm", duration=10, clean=TRUE, top = TRUE, dir = dirout) + ReturnVal <- tkmessageBox(title="RGL 3 D",message="Fini !",icon="info",type="ok") + } + + require(tcltk) + ReturnVal <- tkmessageBox(title="RGL 3 D",message="Cliquez pour fermer",icon="info",type="ok") + rgl.close() +} diff --git a/Rscripts/afc_lex.R b/Rscripts/afc_lex.R new file mode 100644 index 0000000..b84b4b1 --- /dev/null +++ b/Rscripts/afc_lex.R @@ -0,0 +1,33 @@ +#Author: Pierre Ratinaud +#Copyright (c) 20011 Pierre Ratinaud +#Lisense: GNU/GPL + + +#fichier genere par IRaMuTeq +source('%s') +load('%s') +doafc <- TRUE +nd <- %i +filename <-'%s' +width <- %i +height <- %i +facteurs <- '%s' +lignes <- '%s' +colonnes <- '%s' + +filein <- 'tableafcm.csv' +library(ca) + +if (doafc) { + cont <- read.csv2('tableafcm.csv', header = TRUE, row.names = 1, quote='"') + afc <- ca(cont, nd=nd) + afc <- AddCorrelationOk(afc) + afc <- summary.ca.dm(afc) + afc_table <- create_afc_table(afc) + write.csv2(afc_table$facteur, file = facteurs) + write.csv2(afc_table$colonne, file = colonnes) + write.csv2(afc_table$ligne, file = lignes) +} + +#open_file_graph(filename, width = width, height = height) +#make_afc_graph(toplot,classes,clnb, xlab, ylab, cex_txt = NULL) diff --git a/Rscripts/anacor.R b/Rscripts/anacor.R new file mode 100644 index 0000000..c68df77 --- /dev/null +++ b/Rscripts/anacor.R @@ -0,0 +1,104 @@ +print('NEW SVD') +################################################################################# +#http://www.mail-archive.com/rcpp-devel@lists.r-forge.r-project.org/msg01513.html + +write.sparse <- function (m, to) { + ## Writes in a format that SVDLIBC can read + stopifnot(inherits(m, "dgCMatrix")) + fh <- file(to, open="w") + + wl <- function(...) cat(..., "\n", file=fh) + + ## header + wl(dim(m), length(m@x)) + + globalCount <- 1 + nper <- diff(m@p) + for(i in 1:ncol(m)) { + wl(nper[i]) ## Number of entries in this column + if (nper[i]==0) next + for(j in 1:nper[i]) { + wl(m@i[globalCount], m@x[m@p[i]+j]) + globalCount <- globalCount+1 + } + } +} + +my.svd <- function(x, nu, nv, libsvdc.path=NULL, sparse.path=NULL) { + print('my.svd') + stopifnot(nu==nv) + outfile <- file.path(tempdir(),'sout') + cmd <- paste(libsvdc.path, '-o', outfile, '-d') + #rc <- system(paste("/usr/bin/svd -o /tmp/sout -d", nu, "/tmp/sparse.m")) + rc <- system(paste(cmd, nu, sparse.path)) + if (rc != 0) + stop("Couldn't run external svd code") + res1 <- paste(outfile,'-S', sep='') + d <- scan(res1, skip=1) +#FIXME : sometimes, libsvdc doesn't find solution with 2 dimensions, but does with 3 + if (length(d)==1) { + nu <- nu + 1 + #rc <- system(paste("/usr/bin/svd -o /tmp/sout -d", nu, "/tmp/sparse.m")) + rc <- system(paste(cmd, nu, sparse.path)) + d <- scan(res1, skip=1) + } + utfile <- paste(outfile, '-Ut', sep='') + ut <- matrix(scan(utfile,skip=1),nrow=nu,byrow=TRUE) + if (nrow(ut)==3) { + ut <- ut[-3,] + } + vt <- NULL + list(d=d, u=-t(ut), v=vt) +} +################################################################################### + +#from anacor package +boostana<-function (tab, ndim = 2, libsvdc = FALSE, libsvdc.path=NULL) +{ + #tab <- as.matrix(tab) + if (ndim > min(dim(tab)) - 1) + stop("Too many dimensions!") + name <- deparse(substitute(tab)) + if (any(is.na(tab))) + print('YA NA') + #tab <- reconstitute(tab, eps = eps) + n <- dim(tab)[1] + m <- dim(tab)[2] + N <- sum(tab) + #tab <- as.matrix(tab) + #prop <- as.vector(t(tab))/N + r <- rowSums(tab) + c <- colSums(tab) + qdim <- ndim + 1 + r <- ifelse(r == 0, 1, r) + c <- ifelse(c == 0, 1, c) + print('make z') + z1 <- t(tab)/sqrt(c) + z2 <- tab/sqrt(r) + z <- t(z1) * z2 + if (libsvdc) { + #START NEW SVD + z <- as(z, "dgCMatrix") + tmpmat <- tempfile(pattern='sparse') + print('write sparse matrix') + write.sparse(z, tmpmat) + print('do svd') + sv <- my.svd(z, qdim, qdim, libsvdc.path=libsvdc.path, sparse.path=tmpmat) + #END NEW SVD + } else { + print('start R svd') + sv <- svd(z, nu = qdim, nv = qdim) + print('end svd') + } + sigmavec <- (sv$d)[2:qdim] + x <- ((sv$u)/sqrt(r))[, -1] + x <- x * sqrt(N) + x <- x * outer(rep(1, n), sigmavec) + dimlab <- paste("D", 1:ndim, sep = "") + colnames(x) <- dimlab# <- colnames(y) <- dimlab + rownames(x) <- rownames(tab) + result <- list(ndim = ndim, row.scores = x, + singular.values = sigmavec, eigen.values = sigmavec^2) + class(result) <- "boostanacor" + result +} diff --git a/Rscripts/association.R b/Rscripts/association.R new file mode 100644 index 0000000..fb1c7eb --- /dev/null +++ b/Rscripts/association.R @@ -0,0 +1,65 @@ +#author : Pierre Ratinaud +#copyright 1012 Pierre Ratinaud +#license : GNU GPL + + +in.table <- read.csv2('/home/pierre/workspace/iramuteq/corpus/association_suede.csv', header = FALSE, row.names=1) +source('/home/pierre/workspace/iramuteq/Rscripts/Rgraph.R') + +verges.table <- function(x, freq.ts = 'mean', rank.ts = 'mean') { +#x matrice : eff, rank +#table.out : eff in rows, rank in columns +# 1|2 +# 3|4 + if (freq.ts == 'mean') { + freq.ts <- mean(x[,1]) + } + if (rank.ts == 'mean') { + rank.ts <- mean(x[,2]) + } + + eff.cex=c(0.8,2) + rank.cex=NULL + + if (!is.null(eff.cex)) x <- cbind(x,norm.eff=norm.vec(x[,1],eff.cex[1], eff.cex[2])) + + if (!is.null(rank.cex)) x <- cbind(x,norm.rank=norm.vec(x[,1],rank.cex[1], rank.cex[2])) + + case.1 <- x[which(x[,1] >= freq.ts & x[,2] <= rank.ts),] + case.2 <- x[which(x[,1] >= freq.ts & x[,2] > rank.ts),] + case.3 <- x[which(x[,1] < freq.ts & x[,2] <= rank.ts),] + case.4 <- x[which(x[,1] < freq.ts & x[,2] > rank.ts),] + + ylims <- max(c(nrow(case.1), nrow(case.2), nrow(case.3) ,nrow(case.4))) + + + plot.case <- function(case) { + txt <- rownames(case) + if (ncol(case) == 3) lab.cex <- case[,3] + else lab.cex=1 + plot(rep(1,length(txt)),1:length(txt),pch='',axes=FALSE, xlab='', ylab='', ylim = c(0,ylims)) + ys <- ylims - length(txt) + ys <- (length(txt):1) + ys + text(1,ys,txt, xlab=NULL, ylab=NULL, cex=lab.cex) + } + + boxcolor = 'green' + par(mfcol=c(2,2)) + par(mar=c(1,1,1,1)) + par(oma=c(3,3,3,3)) + plot.case(case.1) + box("figure", col=boxcolor) + plot.case(case.3) + box("figure", col=boxcolor) + plot.case(case.2) + box("figure", col=boxcolor) + plot.case(case.4) + box("figure", col=boxcolor) + + mtext("rangs <= | rangs >", side=3, line=1, cex=1, col="blue", outer=TRUE) + mtext("fréquences < | fréquences >=", side=2, line=2, cex=1, col="blue", outer=TRUE) + box("outer", col="blue") +} + + + diff --git a/Rscripts/chd.R b/Rscripts/chd.R new file mode 100644 index 0000000..9d79ac6 --- /dev/null +++ b/Rscripts/chd.R @@ -0,0 +1,87 @@ +#chd.R +source('%s') +source('%s') +source('%s') +source('%s') +classif.mode <- %i +file.data1 <- '%s' +file.data2 <- '%s' +file.listuce1 <- '%s' +file.listuce2 <- '%s' +file.uce <- '%s' +mincl <- %i +graph.arbre1 <- '%s' +graph.arbre2 <- '%s' +graph.dendro1 <- '%s' +graph.dendro2 <- '%s' +data.out <- %s + +data1<-read.csv2(file.data1, header = FALSE) + +if (classif.mode == 0) { + data2<-read.csv2(file.data2, header = FALSE) +} +chd1<-CHD(data1) + +if (classif.mode == 0) { + chd2<-CHD(data2) +} else { + chd2<-chd1 +} + +#lecture des uce +listuce1<-read.csv2(file.listuce1) + +if (classif.mode == 0) { + listuce2<-read.csv2(file.listuce2) +} + +rm(data1) + +if (classif.mode == 0) rm(data2) + +chd.result <- Rchdtxt(file.uce,mincl=mincl,classif_mode=classif.mode) +n1 <- chd.result$n1 +classeuce1 <- chd.result$cuce1 +classeuce2 <- chd.result$cuce2 + +tree.tot1 <- make_tree_tot(chd1) +open_file_graph(graph.arbre1, widt = 600, height=400) +plot(tree.tot1$tree.cl) +dev.off() + +if (classif.mode == 0) { + tree.tot2 <- make_tree_tot(chd2) + open_file_graph(graph.arbre2, width = 600, height=400) + plot(tree.tot2$tree.cl) + dev.off() +} +tree.cut1 <- make_dendro_cut_tuple(tree.tot1$dendro_tuple, chd.result$coord_ok, classeuce1, 1) +classes<-n1[,9] +open_file_graph(graph.dendro1, width = 600, height=400) +plot.dendropr(tree.cut1$tree.cl,classes) +dev.off() + +if (classif.mode == 0) { + tree.cut2 <- make_dendro_cut_tuple(tree.tot2$dendro_tuple, chd.result$coord_ok, classeuce2, 2) + open_file_graph(graph.dendro2, width = 600, height=400) + plot(tree.cut2$tree.cl) + dev.off() +} +save.image(file=data.out) + + +(RscriptPath['CHD'], RscriptPath['chdtxt'], RscriptPath['anacor'], RscriptPath['Rgraph']) + + """ % DicoPath['TableUc1'] + + """ % DicoPath['TableUc2'] +""" % DicoPath['listeuce1'] + + """ % DicoPath['listeuce2'] +""" % (DicoPath['uce'], mincl, classif_mode) +"""%DicoPath['arbre1'] + """ %DicoPath['arbre2'] +""" % DicoPath['dendro1'] + """ % DicoPath['dendro2'] +% DicoPath['RData'] diff --git a/Rscripts/chdfunct.R b/Rscripts/chdfunct.R new file mode 100644 index 0000000..2ca1eac --- /dev/null +++ b/Rscripts/chdfunct.R @@ -0,0 +1,622 @@ +#datadm<-read.table('/home/pierre/.hippasos/corpus_agir_CHDS_16/fileACTtemp.csv', header=TRUE,sep=';', quote='\"',row.names = 1, na.strings = 'NA') +library(cluster) +#dissmat<-daisy(dataact, metric = 'gower', stand = FALSE) +#chd<-diana(dissmat,diss=TRUE,) +#height<-chd$height +#sortheight<-sort(height,decreasing=TRUE) +FindBestCluster<-function (x,Max=15) { + i<-1 + j<-1 + ListClasseOk<-list() + while (i < Max) { + if (x[i]==1){ + while (x[i]==1) { + i<-i+1 + } + ListClasseOk[[j]]<-i + j<-j+1 + } + if (x[i]==x[i+1]) { + i<-i+1 + } + else { + ListClasseOk[[j]]<-i+1 + i<-i+1 + j<-j+1 + } + } + unlist(ListClasseOk) +} +#BestCLusterNb<-FindBestCluster(sortheight) +#classes<-as.data.frame(cutree(as.hclust(chd), k=6))[,1] +#datadm<-cbind(datadm,classes) +#clusplot(datadm,classes,shade=TRUE,color=TRUE,labels=4) + +BuildContTable<- function (x) { + afctable<-NULL + for (i in 1:(ncol(x)-1)) { + coltable<-table(x[,i],x$classes) + afctable<-rbind(afctable,coltable) + } + afctable +} + +PrintProfile<- function(dataclasse,profileactlist,profileetlist,antiproact,antiproet,clusternb,profileout,antiproout,profilesuplist=NULL,antiprosup=NULL) { + prolist<-list() + profile<-matrix(,0,6) + antipro<-matrix(,0,6) + cltot<-as.data.frame(dataclasse[dataclasse[,ncol(dataclasse)]!=0,]) + cltot<-as.data.frame(as.character(cltot[,ncol(cltot)])) + tot<-nrow(cltot) + classes<-as.data.frame(as.character(dataclasse[,ncol(dataclasse)])) + classes.s<-as.data.frame(summary(cltot[,1],maxsum=500)) + profile<-rbind(profile,c('***','nb classes',clusternb,'***','','')) + antipro<-rbind(antipro,c('***','nb classes',clusternb,'***','','')) + for(i in 1:clusternb) { + profile<-rbind(profile,c('**','classe',i,'**','','')) + nbind<-classes.s[which(rownames(classes.s)==i),1] + pr<-round((nbind/tot)*100,digits=2) + profile<-rbind(profile,c('****',nbind,tot,pr,'****','')) + if (length(profileactlist[[1]][[i]])!=0){ + profile<-rbind(profile,as.matrix(profileactlist[[1]][[i]])) + } + if (!is.null(profilesuplist)) { + profile<-rbind(profile,c('*****','*','*','*','*','*')) + if (length(profilesuplist[[1]][[i]])!=0) { + profile<-rbind(profile,as.matrix(profilesuplist[[1]][[i]])) + } + } + if (!is.null(profileetlist)) { + profile<-rbind(profile,c('*','*','*','*','*','*')) + if (length(profileetlist[[1]][[i]])!=0) { + profile<-rbind(profile,as.matrix(profileetlist[[1]][[i]])) + } + } + antipro<-rbind(antipro,c('**','classe',i,'**','','')) + antipro<-rbind(antipro,c('****',nbind,tot,pr,'****','')) + if (length(antiproact[[1]][[i]])!=0) { + antipro<-rbind(antipro,as.matrix(antiproact[[1]][[i]])) + } + if (!is.null(profilesuplist)) { + antipro<-rbind(antipro,c('*****','*','*','*','*','*')) + if (length(antiprosup[[1]][[i]])!=0) { + antipro<-rbind(antipro,as.matrix(antiprosup[[1]][[i]])) + } + } + if (!is.null(profileetlist)) { + antipro<-rbind(antipro,c('*','*','*','*','*','*')) + if (length(antiproet[[1]][[i]])!=0) { + antipro<-rbind(antipro,as.matrix(antiproet[[1]][[i]])) + } + } + } + write.csv2(profile,file=profileout,row.names=FALSE) + write.csv2(antipro,file=antiproout,row.names=FALSE) +} + +AddCorrelationOk<-function(afc) { + rowcoord<-afc$rowcoord + colcoord<-afc$colcoord + factor <- ncol(rowcoord) + hypo<-function(rowcoord,ligne) { + somme<-0 + for (i in 1:factor) { + somme<-somme+(rowcoord[ligne,i])^2 + } + sqrt(somme) + } + cor<-function(d,hypo) { + d/hypo + } + CompCrl<-function(rowcol) { + out<-rowcol + for (i in 1:factor) { + for(ligne in 1:nrow(rowcol)) { + out[ligne,i]<-cor(rowcol[ligne,i],hypo(rowcol,ligne)) + } + } + out + } + afc$rowcrl<-CompCrl(rowcoord) + afc$colcrl<-CompCrl(colcoord) + afc +} + +AsLexico<- function(x) { + x<-as.matrix(x) + sumcol<-colSums(x) + sumrow<-rowSums(x) + tot<-sum(sumrow) + tablesqr<-x + tablep<-x + mod.names<-rownames(x) + #problem exemple aurelia + for (classe in 1:ncol(x)) { + print(classe) + for (ligne in 1:nrow(x)) { + conttable<-matrix(0,2,2) + conttable[1,1]<-as.numeric(x[ligne,classe]) + conttable[1,2]<-sumrow[ligne]-conttable[1,1] + conttable[2,1]<-sumcol[classe]-conttable[1,1] + conttable[2,2]<-tot-sumrow[ligne]-conttable[2,1] + chiresult<-chisq.test(conttable,correct=TRUE) + if (is.na(chiresult$p.value)) { + chiresult$p.value<-1 + chiresult$statistic<-0 + } + obsv<-chiresult$expected + pval<-as.character(format(chiresult$p.value,scientific=TRUE)) + spval<-strsplit(pval,'e') + if (is.na(spval)) { + print(spval) + } + if (conttable[1,1]>obsv[1,1]) { + tablep[ligne,classe]<-as.numeric(spval[[1]][2])*(-1) + tablesqr[ligne,classe]<-round(chiresult$statistic,digits=3) + } + else if (conttable[1,1]obsv[1,1]) { + as.numeric(spval[[1]][2])*(-1) + } + else if (tb[1,1]obsv[1,1]) { + chiresult$p.value + } + else if (tb[1,1]obsv[1,1]) { + chiresult$statistic + } + else if (tb[1,1] nrow(lexicaltable)) + stop("Row index must be smaller than the number of rows.") + } + lexicaltable <- lexicaltable[types, , drop = FALSE] + rowMargin <- rowMargin[types] + } + if (!is.null(parts)) { + if (is.character(parts)) { + if (is.null(colnames(lexicaltable))) + stop("The lexical table has no col names and the \"parts\" argument is a character vector.") + if (!all(parts %in% colnames(lexicaltable))) + stop(paste("Some requested parts are not known in the lexical table: ", + paste(parts[!(parts %in% colnames(lexicaltable))], + collapse = " "))) + } + else { + if (max(parts) > ncol(lexicaltable)) + stop("Column index must be smaller than the number of cols.") + if (any(parts < 1)) + stop("The col index must be greater than 0.") + } + lexicaltable <- lexicaltable[, parts, drop = FALSE] + colMargin <- colMargin[parts] + } + if (nrow(lexicaltable) == 0 | ncol(lexicaltable) == 0) { + stop("The lexical table must contains at least one row and one column.") + } + specif <- matrix(0, nrow = nrow(lexicaltable), ncol = ncol(lexicaltable)) + for (i in 1:ncol(lexicaltable)) { + whiteDrawn <- lexicaltable[, i] + white <- rowMargin + black <- F - white + drawn <- colMargin[i] + independance <- (white * drawn)/F + specif_negative <- whiteDrawn < independance + specif_positive <- whiteDrawn >= independance + specif[specif_negative, i] <- phyper(whiteDrawn[specif_negative], + white[specif_negative], black[specif_negative], drawn) + specif[specif_positive, i] <- phyper(whiteDrawn[specif_positive] - + 1, white[specif_positive], black[specif_positive], + drawn) + } + dimnames(specif) <- dimnames(lexicaltable) + return(specif) +} + +#from textometrieR +#http://txm.sourceforge.net/doc/R/textometrieR-package.html +#Sylvain Loiseau +specificites <- function (lexicaltable, types = NULL, parts = NULL) +{ + spe <- specificites.probabilities(lexicaltable, types, parts) + spelog <- matrix(0, nrow = nrow(spe), ncol = ncol(spe)) + spelog[spe < 0.5] <- log10(spe[spe < 0.5]) + spelog[spe > 0.5] <- abs(log10(1 - spe[spe > 0.5])) + spelog[spe == 0.5] <- 0 + spelog[is.infinite(spe)] <- 0 + spelog <- round(spelog, digits = 4) + rownames(spelog) <- rownames(spe) + colnames(spelog) <- colnames(spe) + return(spelog) +} + +make.spec.hypergeo <- function(mat) { + #library(textometrieR) + spec <- specificites(mat) + sumcol<-colSums(mat) + eff_relatif<-round(t(apply(mat,1,function(x) {(x/t(as.matrix(sumcol))*1000)})),2) + out <-list() + out[[1]]<-spec + out[[3]]<-eff_relatif + out +} + +BuildProf01<-function(x,classes) { + #x : donnees en 0/1 + #classes : classes de chaque lignes de x + dm<-cbind(x,cl=classes) + clnb=length(summary(as.data.frame(as.character(classes)),max=100)) + mat<-matrix(0,ncol(x),clnb) + rownames(mat)<-colnames(x) + for (i in 1:clnb) { + dtmp<-dm[which(dm$cl==i),] + for (j in 1:(ncol(dtmp)-1)) { + mat[j,i]<-sum(dtmp[,j]) + } + } + mat +} + +BuildProf<- function(x,dataclasse,clusternb,lim=2) { + #### + #r.names<-rownames(x) + #x<-as.matrix(x) + #rownames(x)<-r.names + #### + #nuce<-nrow(dataclasse) + sumcol<-paste(NULL,1:nrow(x)) + colclasse<-dataclasse[,ncol(dataclasse)] + nuce <- length(which(colclasse != 0)) +# for (i in 1:nrow(x)) { +# sumcol[i]<-sum(x[i,]) +# } +# afctablesum<-cbind(x,sumcol) + afctablesum <- cbind(x, rowSums(x)) + #dataclasse<-as.data.frame(dataclasse[dataclasse[,ncol(dataclasse)]!=0,]) + dataclasse<-as.matrix(dataclasse[dataclasse[,ncol(dataclasse)]!=0,]) + tablesqr<-x + tablep<-x + x<-afctablesum + listprofile<-list() + listantipro<-list() + mod.names<-rownames(x) + prof<-list() + aprof<-list() + lnbligne<-matrix() + for (classe in 1:clusternb) { + lnbligne[classe]<-length(colclasse[colclasse==classe]) + prof[[classe]]<-data.frame() + aprof[[classe]]<-data.frame() + } + + for (ligne in 1:nrow(x)) { + for (classe in 1:clusternb) { + nbligneclasse<-lnbligne[classe] + conttable<-data.frame() + conttable[1,1]<-as.numeric(x[ligne,classe]) + conttable[1,2]<-as.numeric(as.vector(x[ligne,ncol(x)]))-as.numeric(x[ligne,classe]) + conttable[2,1]<-nbligneclasse-as.numeric(x[ligne,classe]) + conttable[2,2]<-nrow(dataclasse)-as.numeric(as.vector(x[ligne,ncol(x)]))-conttable[2,1] + chiresult<-chisq.test(conttable,correct=FALSE) + if (is.na(chiresult$p.value)) { + chiresult$p.value<-1 + chiresult$statistic<-0 + china=TRUE + } + obsv<-chiresult$expected + tablep[ligne,classe]<-round(chiresult$p.value,digits=3) + #tablep[ligne,classe]<-format(chiresult$p.value, scientific=TRUE) + if (chiresult$statistic>=lim) { + if (conttable[1,1]>obsv[1,1]) { + tablesqr[ligne,classe]<-round(chiresult$statistic,digits=3) + prof[[classe]][nrow(prof[[classe]])+1,1]<-as.numeric(x[ligne,classe]) + prof[[classe]][nrow(prof[[classe]]),2]<-as.numeric(as.vector(x[ligne,ncol(x)])) + prof[[classe]][nrow(prof[[classe]]),3]<-round((as.numeric(as.vector(x[ligne,classe]))/as.numeric(as.vector(x[ligne,ncol(x)])))*100,digits=2) + prof[[classe]][nrow(prof[[classe]]),4]<-round(chiresult$statistic,digits=2) + prof[[classe]][nrow(prof[[classe]]),5]<-mod.names[ligne] + prof[[classe]][nrow(prof[[classe]]),6]<-chiresult$p.value + } + else if (conttable[1,1]obsv[1,1]) { + tablesqr[ligne,classe]<-round(chiresult$statistic,digits=3) + } else if (conttable[1,1]=lim) { + if (conttable[1,1]>obsv[1,1]) { + tablesqr[ligne,classe]<-round(chiresult$statistic,digits=3) + prof[[classe]][nrow(prof[[classe]])+1,1]<-as.numeric(x[ligne,classe]) + prof[[classe]][nrow(prof[[classe]]),2]<-as.numeric(as.vector(x[ligne,ncol(x)])) + prof[[classe]][nrow(prof[[classe]]),3]<-round((as.numeric(as.vector(x[ligne,classe]))/as.numeric(as.vector(x[ligne,ncol(x)])))*100,digits=2) + prof[[classe]][nrow(prof[[classe]]),4]<-round(chiresult$statistic,digits=2) + prof[[classe]][nrow(prof[[classe]]),5]<-mod.names[ligne] + prof[[classe]][nrow(prof[[classe]]),6]<-chiresult$p.value + } + else if (conttable[1,1]obsv[1,1]) { + tablesqr[ligne,classe]<-round(chiresult$statistic,digits=3) + } else if (conttable[1,1]=classe] + listf<-unique(listf) + listf +} + +#fonction pour la double classification +#cette fonction doit etre splitter en 4 ou 5 fonctions + +Rchdquest<-function(tableuc1,listeuce1,uceout ,nbt = 9, mincl = 2) { + #source('/home/pierre/workspace/iramuteq/Rscripts/CHD.R') + + #lecture des tableaux + data1<-read.csv2(tableuc1)#,row.names=1) + cn.data1 <- colnames(data1) + data1 <- as.matrix(data1) + colnames(data1) <- cn.data1 + rownames(data1) <- 1:nrow(data1) + data2<-data1 + sc<-colSums(data2) + if (min(sc)<=4){ + data1<-data1[,-which(sc<=4)] + sc<-sc[-which(sc<=4)] + } + #analyse des tableaux avec la fonction CHD qui doit etre sourcee avant + chd1<-CHD(data1, x = nbt) + chd2<-chd1 + + #FIXME: le nombre de classe peut etre inferieur + nbcl <- nbt + 1 + tcl <- ((nbt+1) * 2) - 2 + + #lecture des uce + listuce1<-read.csv2(listeuce1) + listuce2<-listuce1 + + #Une fonction pour assigner une classe a chaque UCE + AssignClasseToUce<-function(listuce,chd) { + out<-matrix(nrow=nrow(listuce),ncol=ncol(chd)) + for (i in 1:nrow(listuce)) { + for (j in 1:ncol(chd)) { + out[i,j]<-chd[(listuce[i,2]+1),j] + } + } + out + } + + #Assignation des classes + classeuce1<-AssignClasseToUce(listuce1,chd1$n1) + classeuce2<-classeuce1 + + #calcul des poids (effectifs) + poids1<-vector(mode='integer',length=tcl) + makepoids<-function(classeuce,poids) { + for (classes in 2:(tcl + 1)){ + for (i in 1:ncol(classeuce)) { + if (poids[(classes-1)]1) { + maxchi<-0 + best<-NULL + for (i in 1:length(listcoordok)) { + chi<-NULL + uce<-NULL + if (nrow(listcoordok[[i]])==maxcl) { + for (j in 1:nrow(listcoordok[[i]])) { + chi<-c(chi,croise[(listcoordok[[i]][j,1]-1),(listcoordok[[i]][j,2]-1)]) + uce<-c(uce,chicroiseori[(listcoordok[[i]][j,1]-1),(listcoordok[[i]][j,2]-1)]) + } + print(sum(uce)) + if (maxchi < sum(chi)) { + maxchi<-sum(chi) + suce<-sum(uce) + best<-i + } + } + } + } + print((suce/nrow(classeuce1)*100)) + listcoordok[[best]] + } + #findmaxclasse(listx,listy) + #coordok<-trouvecoordok(1) + coordok<-findmaxclasse(listx,listy) + print(coordok) + + fille<-function(classe,classeuce) { + listfm<-unique(unlist(classeuce[classeuce[,classe%/%2]==classe,])) + listf<-listfm[listfm>=classe] + listf<-unique(listf) + listf + } + + + lfilletot<-function(classeuce) { + listfille<-NULL + for (classe in 1:nrow(coordok)) { + listfille<-unique(c(listfille,fille(coordok[classe,1],classeuce))) + listfille + } + } + + listfille1<-lfilletot(classeuce1) + listfille2<-lfilletot(classeuce2) + + + #utiliser rownames comme coordonnees dans un tableau de 0 + Assignclasse<-function(classeuce,x) { + nchd<-matrix(0,ncol=ncol(classeuce),nrow=nrow(classeuce)) + for (classe in 1:nrow(coordok)) { + + clnb<-coordok[classe,x] + colnb<-clnb%/%2 + tochange<-which(classeuce[,colnb]==clnb) + for (row in 1:length(tochange)) { + nchd[tochange[row],colnb:ncol(nchd)]<-classe + } + } + nchd + } + print('commence assigne new classe') + nchd1<-Assignclasse(classeuce1,1) + #nchd1<-Assignnewclasse(classeuce1,1) + nchd2<-Assignclasse(classeuce2,2) + print('fini assign new classe') + croisep<-matrix(ncol=nrow(coordok),nrow=nrow(coordok)) + for (i in 1:nrow(nchd1)) { + if (nchd1[i,ncol(nchd1)]==0) { + nchd2[i,]<-nchd2[i,]*0 + } + if (nchd1[i,ncol(nchd1)]!=nchd2[i,ncol(nchd2)]) { + nchd2[i,]<-nchd2[i,]*0 + } + if (nchd2[i,ncol(nchd2)]==0) { + nchd1[i,]<-nchd1[i,]*0 + } + } + print('fini croise') + elim<-which(nchd1[,ncol(nchd1)]==0) + keep<-which(nchd1[,ncol(nchd1)]!=0) + n1<-nchd1[nchd1[,ncol(nchd1)]!=0,] + n2<-nchd2[nchd2[,ncol(nchd2)]!=0,] + print('debut graph') + clnb<-nrow(coordok) + print('fini') + write.csv2(nchd1[,ncol(nchd1)],uceout) + res <- list(n1 = nchd1, coord_ok = coordok, cuce1 = classeuce1, chd = chd1) + res +} +#n1<-Rchdtxt('/home/pierre/workspace/iramuteq/corpus/agir2sortie01.csv','/home/pierre/workspace/iramuteq/corpus/testuce.csv','/home/pierre/workspace/iramuteq/corpus/testuceout.csv') diff --git a/Rscripts/chdtxt.R b/Rscripts/chdtxt.R new file mode 100644 index 0000000..a6c2d4c --- /dev/null +++ b/Rscripts/chdtxt.R @@ -0,0 +1,318 @@ +#Author: Pierre Ratinaud +#Copyright (c) 2008-2009 Pierre Ratinaud +#Lisense: GNU/GPL + + +#fonction pour la double classification +#cette fonction doit etre splitter en 4 ou 5 fonctions + +#Rchdtxt<-function(tableuc1,tableuc2,listeuce1,listeuce2,arbre1,arbre2,uceout) { + #source('/home/pierre/workspace/iramuteq/Rscripts/CHD.R') + + #lecture des tableaux +# data1<-read.csv2(tableuc1) +# data2<-read.csv2(tableuc2) + + #analyse des tableaux avec la fonction CHD qui doit etre sourcee avant +# chd1<-CHD(data1) +# chd2<-CHD(data2) + + #lecture des uce +# listuce1<-read.csv2(listeuce1) +# listuce2<-read.csv2(listeuce2) + + #Une fonction pour assigner une classe a chaque UCE +#AssignClasseToUce<-function(listuce,chd) { +# out<-matrix(nrow=nrow(listuce),ncol=ncol(chd)) +# for (i in 1:nrow(listuce)) { +# for (j in 1:ncol(chd)) { +# out[i,j]<-chd[(listuce[i,2]+1),j] +# } +# } +# out +#} + +AssignClasseToUce <- function(listuce, chd) { + print('assigne classe -> uce') + out<-matrix(nrow=nrow(listuce),ncol=ncol(chd)) + for (j in 1:ncol(chd)) { + out[listuce[,1]+1, j] <- chd[listuce[,2]+1, j] + } + out +} + +fille<-function(classe,classeuce) { + listfm<-unique(unlist(classeuce[classeuce[,classe%/%2]==classe,])) + listf<-listfm[listfm>=classe] + listf<-unique(listf) + listf +} +#nbt nbcl = nbt+1 tcl=((nbt+1) *2) - 2 n1[,ncol(n1)], nchd1[,ncol(nchd1)] +Rchdtxt<-function(uceout,mincl=0,classif_mode=0, nbt = 9) { + #FIXME: le nombre de classe peut etre inferieur + nbcl <- nbt + 1 + tcl <- ((nbt+1) * 2) - 2 + #Assignation des classes + classeuce1<-AssignClasseToUce(listuce1,chd1$n1) + if (classif_mode==0) { + classeuce2<-AssignClasseToUce(listuce2,chd2$n1) + } else { + classeuce2<-classeuce1 + } + + #calcul des poids (effectifs) + + makepoids<-function(classeuce,poids) { + for (classes in 2:(tcl + 1)){ + for (i in 1:ncol(classeuce)) { + if (poids[(classes-1)]1) { + maxchi<-0 + best<-NULL + for (i in 1:length(listcoordok)) { + chi<-NULL + uce<-NULL + if (nrow(listcoordok[[i]])==maxcl) { + for (j in 1:nrow(listcoordok[[i]])) { + chi<-c(chi,croise[(listcoordok[[i]][j,1]-1),(listcoordok[[i]][j,2]-1)]) + uce<-c(uce,chicroiseori[(listcoordok[[i]][j,1]-1),(listcoordok[[i]][j,2]-1)]) + } + if (maxchi < sum(chi)) { + maxchi<-sum(chi) + suce<-sum(uce) + best<-i + } + } + } + } + print((suce/nrow(classeuce1)*100)) + listcoordok[[best]] + } + #findmaxclasse(listx,listy) + #coordok<-trouvecoordok(1) + coordok<-findmaxclasse(listx,listy) + print(coordok) + + lfilletot<-function(classeuce,x) { + listfille<-NULL + for (classe in 1:nrow(coordok)) { + listfille<-unique(c(listfille,fille(coordok[classe,x],classeuce))) + listfille + } + } + + listfille1<-lfilletot(classeuce1,1) + listfille2<-lfilletot(classeuce2,2) + + #utiliser rownames comme coordonnees dans un tableau de 0 + Assignclasse<-function(classeuce,x) { + nchd<-matrix(0,ncol=ncol(classeuce),nrow=nrow(classeuce)) + for (classe in 1:nrow(coordok)) { + clnb<-coordok[classe,x] + colnb<-clnb%/%2 + nchd[which(classeuce[,colnb]==clnb), colnb:ncol(nchd)] <- classe + } + nchd + } + print('commence assigne new classe') + nchd1<-Assignclasse(classeuce1,1) + if (classif_mode==0) + nchd2<-Assignclasse(classeuce2,2) + else + nchd2<-nchd1 + print('fini assign new classe') + #croisep<-matrix(ncol=nrow(coordok),nrow=nrow(coordok)) + nchd2[which(nchd1[,ncol(nchd1)]==0),] <- 0 + nchd2[which(nchd1[,ncol(nchd1)]!=nchd2[,ncol(nchd2)]),] <- 0 + nchd1[which(nchd2[,ncol(nchd2)]==0),] <- 0 + +# for (i in 1:nrow(nchd1)) { +# if (nchd1[i,ncol(nchd1)]==0) { +# nchd2[i,]<-nchd2[i,]*0 +# } +# if (nchd1[i,ncol(nchd1)]!=nchd2[i,ncol(nchd2)]) { +# nchd2[i,]<-nchd2[i,]*0 +# } +# if (nchd2[i,ncol(nchd2)]==0) { +# nchd1[i,]<-nchd1[i,]*0 +# } +# } + print('fini croise') + elim<-which(nchd1[,ncol(nchd1)]==0) + keep<-which(nchd1[,ncol(nchd1)]!=0) + n1<-nchd1[nchd1[,ncol(nchd1)]!=0,] + n2<-nchd2[nchd2[,ncol(nchd2)]!=0,] + #clnb<-nrow(coordok) + print('fini') + write.csv2(nchd1[,ncol(nchd1)],uceout) + res <- list(n1 = nchd1, coord_ok = coordok, cuce1 = classeuce1, cuce2 = classeuce2) + res +} diff --git a/Rscripts/graph.txt b/Rscripts/graph.txt new file mode 100644 index 0000000..d2ff433 --- /dev/null +++ b/Rscripts/graph.txt @@ -0,0 +1,148 @@ + [1] 16.1654135 1.0101010 3.2258065 2.1978022 8.5714286 1.0752688 + [7] 4.5871560 1.2820513 3.8461538 1.1904762 8.2926829 1.2048193 + [13] 2.6315789 7.8651685 0.8403361 1.7543860 3.4482759 2.3255814 + [19] 3.5294118 3.8709677 1.8518519 1.1235955 2.5316456 3.0000000 + [25] 10.4000000 11.6564417 2.2988506 6.6666667 3.4883721 6.0240964 + [31] 1.2195122 1.1494253 5.2631579 3.8095238 3.4482759 5.6962025 + [37] 7.7464789 1.2658228 4.1666667 1.0309278 4.7619048 13.9534884 + [43] 1.9417476 2.3809524 2.2727273 11.2244898 1.5564202 18.8552189 + [49] 2.0080321 5.9071730 16.5254237 1.1904762 3.7735849 1.6949153 + [55] 3.7974684 35.3356890 0.4098361 3.7313433 7.0833333 2.9304029 + [61] 2.5925926 2.1276596 4.1493776 1.6326531 3.7500000 8.7837838 + [67] 5.0387597 1.2096774 10.9243697 18.6507937 15.1006711 6.3829787 + [73] 6.6433566 4.1666667 0.4032258 0.8264463 2.0491803 1.6393443 + [79] 4.4609665 2.2727273 0.8163265 5.0675676 14.2857143 15.4411765 + [85] 0.8264463 0.4166667 1.9531250 8.3682008 0.8196721 18.1451613 + [91] 11.1913357 5.1383399 2.9166667 2.4489796 0.4000000 9.3117409 + [97] 2.8571429 2.4390244 1.5384615 3.1250000 1.7543860 5.8823529 +[103] 2.9850746 5.8823529 13.3333333 2.5641026 2.7272727 16.9811321 +[109] 2.4390244 6.4516129 1.8518519 1.5151515 7.8651685 2.5000000 +[115] 2.5641026 2.8571429 7.0175439 13.3333333 1.7094017 0.9615385 +[121] 10.3448276 6.1224490 2.0408163 2.7777778 1.2048193 1.0204082 +[127] 2.7027027 2.4390244 5.0000000 0.7352941 1.5151515 4.6357616 +[133] 4.5454545 8.9655172 1.5748031 26.6666667 0.7874016 6.6225166 +[139] 0.7194245 13.1034483 4.5751634 0.7518797 1.5384615 18.4971098 +[145] 3.3557047 3.0769231 1.6260163 3.5211268 4.5977011 5.1162791 +[151] 2.3076923 0.5347594 2.3076923 2.3255814 0.7692308 3.1446541 +[157] 4.7945205 4.8387097 5.4347826 25.5952381 12.5714286 1.6000000 +[163] 3.5971223 3.6496350 1.5748031 11.3924051 5.5248619 4.1958042 +[169] 4.6875000 3.0769231 2.0000000 1.7241379 0.6024096 1.6393443 +[175] 2.5000000 1.5625000 3.3898305 2.9411765 3.1250000 3.9215686 +[181] 3.7735849 2.9411765 2.1276596 3.7500000 4.0983607 3.4883721 +[187] 3.0303030 6.4516129 10.3448276 6.5573770 1.8867925 1.0638298 +[193] 2.7522936 7.1428571 2.3809524 3.4482759 4.5454545 6.3829787 +[199] 3.3333333 6.0606061 3.8461538 7.6923077 12.5000000 7.8947368 +[205] 5.0000000 3.2258065 11.1111111 0.9803922 1.2658228 1.0989011 +[211] 2.8301887 6.7415730 2.5641026 4.2253521 1.1363636 2.0000000 +[217] 1.2658228 2.0000000 9.9415205 12.7272727 2.2727273 3.7037037 +[223] 3.8461538 0.7936508 9.5238095 6.1224490 2.0689655 7.8431373 +[229] 0.9174312 1.8518519 2.0833333 1.3513514 1.7543860 7.2580645 +[235] 7.2072072 7.6923077 3.6697248 1.4285714 7.1428571 5.2631579 +[241] 3.8461538 4.3478261 8.6206897 5.0847458 4.9019608 2.8571429 +[247] 3.7037037 4.0650407 2.2727273 1.5384615 3.7735849 3.2258065 +[253] 4.3478261 3.6697248 5.3191489 2.3255814 3.3333333 1.2987013 +[259] 5.6818182 5.8823529 1.8518519 2.1739130 3.2967033 2.0408163 +[265] 6.4102564 7.5000000 2.5000000 1.8181818 7.6271186 1.3333333 +[271] 4.0000000 4.9645390 5.7692308 3.8461538 4.8780488 2.7397260 +[277] 1.9607843 7.4074074 3.9062500 8.1818182 2.0833333 2.2222222 +[283] 8.1967213 2.0000000 2.0833333 9.7087379 2.8985507 5.6603774 +[289] 1.8181818 2.7397260 3.8461538 5.0000000 5.5555556 2.5000000 +[295] 2.9850746 1.8018018 1.3513514 1.2048193 1.6129032 6.2500000 +[301] 0.6493506 6.2500000 2.0408163 1.0752688 4.5454545 2.8571429 +[307] 1.4285714 0.8771930 1.3157895 5.0000000 6.0000000 2.4390244 +[313] 3.7500000 3.1250000 2.5641026 4.5454545 3.9215686 8.6956522 +[319] 2.0833333 8.1081081 2.7397260 0.8474576 1.7857143 5.0505051 +[325] 1.1235955 4.5454545 1.2048193 1.2820513 4.3715847 6.2500000 +[331] 8.3798883 3.2608696 2.6845638 1.2345679 0.6211180 3.8461538 +[337] 9.8130841 2.7932961 0.6134969 12.0253165 6.5326633 7.1129707 +[343] 4.4871795 3.3175355 1.2422360 0.6211180 0.6410256 2.1052632 +[349] 5.1724138 1.2658228 5.1643192 12.6168224 14.0703518 2.3529412 +[355] 6.1728395 0.6329114 15.7303371 8.3333333 4.6783626 1.9108280 +[361] 3.1446541 1.8633540 4.5714286 1.9230769 7.6923077 1.9607843 +[367] 3.1914894 0.8547009 7.6923077 4.6511628 4.7619048 2.3809524 +[373] 0.9803922 5.8823529 2.4691358 8.3333333 4.0000000 8.5365854 +[379] 4.9382716 9.2436975 3.9473684 1.7241379 1.4084507 0.9433962 +[385] 2.7210884 7.6190476 3.6363636 5.9523810 11.4285714 7.8431373 +[391] 11.2149533 4.6153846 4.2735043 6.1224490 4.2553191 9.5238095 +[397] 1.8867925 2.0202020 10.4761905 2.7777778 3.5087719 2.7027027 +[403] 0.9259259 2.0000000 4.8780488 3.1746032 2.7777778 3.3707865 +[409] 2.7777778 3.3333333 5.8823529 3.0303030 2.0833333 3.6036036 +[415] 3.0612245 3.8961039 1.0638298 1.7857143 6.0240964 1.6949153 +[421] 14.6551724 1.2345679 1.6393443 1.9230769 0.9174312 1.9867550 +[427] 1.7543860 5.2631579 1.7241379 2.2222222 6.5789474 9.4339623 +[433] 8.9285714 12.0967742 5.0420168 1.8518519 1.9607843 4.3478261 +[439] 9.1743119 6.9444444 1.6393443 3.3333333 9.8039216 5.7377049 +[445] 8.3333333 5.4545455 4.2553191 1.4285714 0.9523810 4.1666667 +[451] 7.6923077 1.7857143 5.6603774 1.8181818 5.7692308 8.6419753 +[457] 10.0000000 3.8461538 10.2803738 3.8461538 1.6806723 6.2500000 +[463] 6.2500000 3.9215686 4.5454545 5.7971014 1.8867925 1.7543860 +[469] 1.7543860 1.0989011 3.0303030 5.2631579 1.2500000 1.2048193 +[475] 1.6129032 2.8571429 0.9803922 4.6511628 1.2658228 4.2016807 +[481] 6.1728395 4.1666667 3.3333333 3.8095238 4.3956044 2.5641026 +[487] 1.3698630 1.1363636 2.1739130 3.2258065 2.0000000 2.0202020 +[493] 13.6363636 2.3809524 1.2987013 3.3898305 3.7037037 3.5714286 +[499] 4.5454545 3.7037037 3.8461538 1.6949153 4.6511628 0.9433962 +[505] 2.7027027 1.1627907 2.2727273 4.0000000 2.0202020 4.8780488 +[511] 4.0000000 1.6666667 2.4390244 4.5454545 1.6949153 5.9405941 +[517] 4.4943820 2.7027027 1.1627907 2.0833333 5.1282051 1.9801980 +[523] 1.0752688 1.7543860 4.1379310 3.7234043 0.9900990 0.6410256 +[529] 2.0202020 2.1276596 1.0000000 1.0101010 3.1007752 7.0175439 +[535] 4.2105263 3.8216561 20.0000000 3.7500000 2.1276596 2.7272727 +[541] 1.8348624 4.2553191 5.8394161 8.8435374 2.6086957 1.0204082 +[547] 3.0000000 6.1855670 3.3898305 2.3809524 1.5625000 2.0408163 +[553] 4.3478261 2.0000000 6.0000000 4.0816327 7.1428571 4.1666667 +[559] 2.0833333 5.1282051 5.9701493 9.8039216 3.2000000 3.6036036 +[565] 2.2727273 2.4390244 5.0847458 3.4482759 1.0752688 8.0645161 +[571] 2.1276596 1.9607843 4.5454545 2.5641026 4.2016807 4.8780488 +[577] 3.4482759 3.5714286 5.0847458 3.7037037 5.7471264 2.8301887 +[583] 4.3956044 4.1666667 4.7619048 2.4390244 1.1363636 2.1739130 +[589] 3.2258065 6.6666667 0.8771930 5.4794521 1.9230769 2.4390244 +[595] 3.7500000 1.1764706 1.2658228 9.5238095 2.4390244 3.3333333 +[601] 2.2388060 12.8205128 2.3255814 5.5555556 1.5873016 0.9615385 +[607] 3.3898305 2.8571429 3.9215686 6.0975610 0.9900990 1.6949153 +[613] 6.8322981 2.5974026 1.5151515 2.5974026 1.2987013 1.3888889 +[619] 4.0000000 2.6666667 5.7692308 1.0204082 2.1897810 4.6666667 +[625] 9.1603053 1.3888889 1.1235955 8.6419753 8.9285714 10.4838710 +[631] 2.1505376 5.5555556 2.5641026 10.0000000 17.8807947 6.0344828 +[637] 1.6666667 3.5398230 1.6666667 4.3103448 10.0000000 4.3795620 +[643] 7.6023392 5.2356021 7.4712644 0.8620690 0.7518797 6.4516129 +[649] 3.4782609 3.7500000 9.6969697 4.5112782 6.1946903 0.8130081 +[655] 1.6393443 10.8527132 1.8867925 3.2967033 2.6315789 12.3076923 +[661] 4.1666667 1.1904762 2.4390244 1.2820513 2.4390244 3.7500000 +[667] 12.6213592 2.9411765 20.6611570 5.1612903 6.4285714 1.2820513 +[673] 1.3333333 5.4945055 2.1739130 2.5316456 3.2258065 5.9259259 +[679] 12.2222222 2.5000000 1.1764706 3.6144578 2.9411765 3.7037037 +[685] 1.6666667 1.8867925 1.0752688 8.3333333 2.8169014 2.3255814 +[691] 4.5454545 8.0000000 3.3333333 2.0408163 3.8461538 1.6949153 +[697] 4.2553191 2.2727273 1.9047619 2.1978022 5.2631579 2.5641026 +[703] 3.5714286 4.6511628 3.4482759 3.4482759 2.0833333 1.1904762 +[709] 0.9900990 1.1494253 3.1250000 5.2631579 17.3913043 1.6949153 +[715] 2.0833333 8.4337349 0.9433962 5.2631579 1.1627907 2.2727273 +[721] 1.7241379 2.2988506 5.7471264 2.6315789 2.3809524 4.1666667 +[727] 5.1948052 3.5714286 12.9629630 3.7313433 3.3057851 3.7735849 +[733] 1.4084507 1.7857143 4.0000000 5.3097345 1.3157895 3.3333333 +[739] 3.3333333 5.1948052 9.9009901 5.7851240 0.8849558 2.5000000 +[745] 3.3898305 1.7241379 4.5454545 3.8461538 1.5384615 4.0816327 +[751] 6.2500000 2.9411765 1.1494253 3.9603960 2.2471910 5.0000000 +[757] 4.5454545 2.9411765 1.1904762 3.7037037 3.7037037 4.3209877 +[763] 2.6490066 3.7974684 8.5106383 2.3529412 2.2900763 9.5588235 +[769] 5.9405941 1.0989011 1.8348624 8.8607595 0.8474576 2.6315789 +[775] 0.9708738 9.4202899 4.4025157 1.6393443 1.9417476 2.8301887 +[781] 2.4000000 1.1494253 2.9411765 1.9801980 2.2727273 9.6000000 +[787] 5.5555556 2.8037383 1.1111111 2.1505376 3.2608696 6.5420561 +[793] 2.9411765 11.1111111 2.5000000 8.6956522 2.3255814 6.6666667 +[799] 13.3333333 1.2820513 2.0408163 12.1212121 1.2048193 3.1250000 +[805] 1.7857143 5.0000000 3.3898305 6.4935065 1.0416667 2.8571429 +[811] 1.7241379 3.8461538 8.0000000 2.3437500 4.7058824 1.3698630 +[817] 1.3698630 4.4943820 2.9702970 1.1904762 1.1363636 3.4883721 +[823] 5.8823529 2.3809524 4.4444444 4.4444444 3.7037037 2.1739130 +[829] 6.6666667 2.0000000 +[1] "arbre maximum" + [1] 16.165414 6.024096 13.953488 11.224490 16.525424 35.335689 18.650794 + [8] 15.100671 15.441176 18.145161 13.333333 16.981132 13.333333 10.344828 +[15] 26.666667 25.595238 10.344828 12.500000 11.111111 12.727273 8.620690 +[22] 9.708738 6.250000 6.250000 8.108108 8.333333 11.428571 11.214953 +[29] 14.655172 9.433962 5.263158 6.172840 13.636364 20.000000 7.142857 +[36] 6.666667 9.523810 12.820513 8.641975 17.880795 12.307692 20.661157 +[43] 12.222222 8.333333 8.000000 17.391304 8.433735 12.962963 11.111111 +[50] 8.695652 13.333333 12.121212 8.000000 diff --git a/Rscripts/multipam.R b/Rscripts/multipam.R new file mode 100644 index 0000000..e9c4c90 --- /dev/null +++ b/Rscripts/multipam.R @@ -0,0 +1,121 @@ +library(cluster) + +#data<-read.table('output/corpus_bin.csv',header=TRUE,sep='\t') + +multipam<-function(data,x=9){ + dataori=data + dtable=data + a<-0 + for (m in 1:length(dtable)) { + if (sum(dtable[m-a])==0) { + print('colonne vide') + dtable<-dtable[,-(m-a)] + a<-a+1 + } + } + for (i in 1:x) { + clnb<-(i*2) + #construction de la matrice des distances + dissmat<-dist(dtable,method='binary')#FIXME: rendre optionnelle la methode + #bipartition + pm<-pam(dissmat,diss=TRUE,k=2) + print(pm) + #listclasse<-ifelse(coordrow<0,paste('CLASSE',clnb,sep=''),paste('CLASSE',clnb+1,sep='')) + #selection de la classe la moins homogène + clusinf<-pm$clusinfo + if (i==1){ + listclust<-clusinf[,2] + } else { + listclust<-rbind(listclust,clusinf[,2]) + } + + listclasse=as.data.frame(pm$clustering)[,1] + #ajout du classement au tableau + dtable<-transform(dtable,cl1=listclasse) + print(dtable) + #lignes concernees + listrownamedtable<-rownames(dtable) + listrownamedtable<-as.integer(listrownamedtable) + newcol<-vector(length=nrow(dataori)) + #remplissage de la nouvelle colonne avec les nouvelles classes + num<-0 + for (ligne in listrownamedtable) { + num<-num+1 + newcol[ligne]<-as.vector(dtable$cl1[num])[1] + } + #recuperation de la classe precedante pour les cases vides + matori<-as.matrix(dataori) + if (i!=1) { + # options(warn=-1) + for (ligne in 1:length(newcol)) { + # print(newcol[ligne]) + if (newcol[ligne]==0) { # ce test renvoie un warning + newcol[ligne]<-matori[ligne,length(matori[1,])] + } + } + # options(warn=0) + } + #print(newcol) + #??????????????????????????????????? + #je ne comprends pas : j'ai vraiment besoin de faire ces deux actions pour ajouter la nouvelle colonne aux donnees ? + #si je ne le fais pas, ca plante... + dataori<-cbind(dataori,newcol) + dataori<-transform(dataori,newcol=newcol) + #??????????????????????????????????? + + #liste des noms de colonne + #colname<-colnames(dataori) + #nom de la derniere colonne + #colname<-colname[length(dataori)] + #la derniere colonne + colclasse<-as.character(dataori[,ncol(dataori)]) + #print(colclasse) + #les modalites de la derniere colonne + classes<-levels(as.factor(colclasse)) + print(classes) + #determination de la classe la plus grande + tailleclasse<-paste(NULL,1:length(classes)) + b<-0 + for (classe in classes) { + b<-b+1 + dtable<-dataori[dataori[length(dataori)]==classe,] + tailleclasse[b]<-length(dtable[,1]) + } + tailleclasse<-as.integer(tailleclasse) + print(tailleclasse) + plusgrand<-which(tailleclasse==max(tailleclasse)) + + #??????????????????????????????????? + #Si 2 classes ont des effectifs egaux, on prend la premiere de la liste... + if (length(plusgrand)>1) { + plusgrand<-plusgrand[1] + } + #???????????????????????????????????? + + #constuction du prochain tableau a analyser + classe<-classes[plusgrand] + dtable<-dataori[dataori[length(dataori)]==classe,] + dtable<-dtable[,1:(length(dtable)-i)] + #elimination des colonnes ne contenant que des 0 + a<-0 + for (m in 1:length(dtable)) { + if (sum(dtable[m-a])==0) { + dtable<-dtable[,-(m-a)] + a<-a+1 + } + } + } + dataori[(length(dataori)-x+1):length(dataori)] +} + +dm<-read.csv2('/home/pierre/fac/maitrise/classification/simi01.csv',row.names=1) +multipam(dm) +#dataout<-CHD(data,9) + +#library(cluster) +#dissmat<-daisy(dataout, metric = 'gower', stand = FALSE) +#chd<-diana(dissmat,diss=TRUE,) + + +#pour tester le type, passer chaque colonne en matice et faire mode(colonne) +#for (i in 1:13) {tmp<-as.matrix(data[i]);print(mode(tmp))} diff --git a/Rscripts/mysvd.R b/Rscripts/mysvd.R new file mode 100644 index 0000000..d936196 --- /dev/null +++ b/Rscripts/mysvd.R @@ -0,0 +1,42 @@ +#http://www.mail-archive.com/rcpp-devel@lists.r-forge.r-project.org/msg01513.html + +write.sparse <- function (m, to) { + ## Writes in a format that SVDLIBC can read + stopifnot(inherits(m, "dgCMatrix")) + fh <- file(to, open="w") + + wl <- function(...) cat(..., "\n", file=fh) + + ## header + wl(dim(m), length(m@x)) + + globalCount <- 1 + nper <- diff(m@p) + for(i in 1:ncol(m)) { + wl(nper[i]) ## Number of entries in this column + if (nper[i]==0) next + for(j in 1:nper[i]) { + wl(m@i[globalCount], m@x[m@p[i]+j]) + globalCount <- globalCount+1 + } + } +} +my.svd <- function(x, nu, nv) { + stopifnot(nu==nv) + rc <- system(paste("/usr/bin/svd -o /tmp/sout -d", nu, "/tmp/sparse.m")) + if (rc != 0) + stop("Couldn't run external svd code") + d <- scan("/tmp/sout-S", skip=1) +#FIXME : sometimes, libsvdc doesn't find solution with 2 dimensions, but does with 3 + if (length(d)==1) { + nu <- nu + 1 + rc <- system(paste("/usr/bin/svd -o /tmp/sout -d", nu, "/tmp/sparse.m")) + d <- scan("/tmp/sout-S", skip=1) + } + ut <- matrix(scan('/tmp/sout-Ut',skip=1),nrow=nu,byrow=TRUE) + if (nrow(ut)==3) { + ut <- ut[-3,] + } + vt <- NULL + list(d=d, u=-t(ut), v=vt) +} diff --git a/Rscripts/pamtxt.R b/Rscripts/pamtxt.R new file mode 100644 index 0000000..c3c3284 --- /dev/null +++ b/Rscripts/pamtxt.R @@ -0,0 +1,29 @@ +AssignClasseToUce<-function(listuce,chd) { + out<-matrix(nrow=nrow(listuce),ncol=ncol(chd)) + for (i in 1:nrow(listuce)) { + for (j in 1:ncol(chd)) { + out[i,j]<-chd[(listuce[i,2]+1),j] + } + } + out +} + + + +pamtxt <- function(dm, listucein, uceout, method = 'binary', clust_type = 'pam', clnb = 3) { + listuce <- read.csv2(listucein) + x <- read.csv2(dm, header = FALSE) + library(cluster) + x <- as.matrix(x) + distmat <- dist(x,method = method) + if (clust_type == 'pam') + cl <- pam(distmat, clnb, diss=TRUE) + else if (clust_type == 'fanny') + cl <- fanny(distmat, clnb, diss=TRUE, memb.exp = 1.001) + cld <- as.data.frame(cl$clustering) + colnames(cld) <- 'classes' + out <- as.data.frame(AssignClasseToUce(listuce,cld)) + write.csv2(out[,1],uceout) + result <- list(uce = out, cl = cl) + result +} diff --git a/Rscripts/plotafcm.R b/Rscripts/plotafcm.R new file mode 100644 index 0000000..f6c973c --- /dev/null +++ b/Rscripts/plotafcm.R @@ -0,0 +1,208 @@ +#library(ca) +#datadm<-read.table('/home/pierre/.hippasos/corpus_agir.csv',header=TRUE,sep='\t',quote='\"') +#data.mjca<-mjca(datadm[,-1],supcol=c(1:12),nd=6) + +tridata<-function(tabletot,nb=10,fnb=2) { + #atrier = x + #sumx<-summary(atrier) + #tabletot<-as.data.frame(sumx[3]) + tabletotf1<-tabletot[order(tabletot[,5]),] + rowkeep1<-tabletotf1[1:nb,] + rowkeep1<-rbind(rowkeep1,tabletotf1[(nrow(tabletotf1)-nb):nrow(tabletotf1),]) + rowf1names<-rownames(rowkeep1) + tabletotf2<-tabletot[order(tabletot[,8]),] + rowkeep2<-tabletotf2[1:nb,] + rowkeep2<-rbind(rowkeep2,tabletotf2[(nrow(tabletotf2)-nb):nrow(tabletotf2),]) + rowf2names<-rownames(rowkeep2) + count<-0 + for (names in 1:length(rowf1names)) { + for (names2 in 1:length(rowf2names)) { + if (rowf1names[names]==rowf2names[names2]) { + rowkeep1<-rowkeep1[-(as.numeric(names)-count),] + count<-count+1 + } + } + } + rowkeep12<-rbind(rowkeep1,rowkeep2) + if (fnb>=3) { + count<-0 + row12names<-rownames(rowkeep12) + tabletotf3<-tabletot[order(tabletot[,14]),] + rowkeep3<-tabletotf1[1:nb,] + rowkeep3<-rbind(rowkeep3,tabletotf3[(nrow(tabletotf3)-nb):nrow(tabletotf3),]) + rowf3names<-rownames(rowkeep3) + for (names in 1:length(row12names)) { + for (names3 in 1:length(rowf3names)) { + if (rowf3names[names3]==row12names[names]) { + rowkeep12<-rowkeep12[-(as.numeric(names)-count),] + count<-count+1 + } + } + } + rowkeep123<-rbind(rowkeep12,rowkeep3) + rowkeep123 + } + else { + rowkeep12 } +} +#rowkeep<-tridata(data.mjca,nb=50,fnb=3) +#matrow<-as.matrix(rowkeep) +#x<-as.numeric(matrow[,5]) +#y<-as.numeric(matrow[,8]) +#z<-as.numeric(matrow[,11]) +#labels<-matrow[,1] +#plot3d(x,y,z,xlab='',ylab='',zlab='',box=FALSE,axes=FALSE) +#text3d(x,y,z,labels) +#rgl.bg(col = c("#aaaaaa", "#99bb99"), front = "lines", box=FALSE, sphere = TRUE) +#rgl.lines(c(range(x)), c(0, 0), c(0, 0), col = "#0000ff") +#rgl.lines(c(0,0),c(range(y)),c(0,0),col = "#0000ff") +#rgl.lines(c(0,0),c(0,0),c(range(z)),col = "#0000ff") +#text3d(range(x)[2]+1,0,0,'f1') +#text3d(0,range(y)[2]+1,0,'f2') +#text3d(0,0,range(z)[2]+1,'f3') +#plot(as.numeric(matrow[,5]),as.numeric(matrow[,8])) +#text(as.numeric(matrow[,5]),as.numeric(matrow[,8]),matrow[,1]) + +tridata2<-function(tabletot,nb=5,fnb=2) { + #atrier = x + #sumx<-summary(atrier) + #tabletot<-as.data.frame(sumx[3]) + tabletotf1<-tabletot[order(tabletot[,1]),] + rowkeep1<-tabletotf1[1:nb,] + rowkeep1<-rbind(rowkeep1,tabletotf1[(nrow(tabletotf1)-nb):nrow(tabletotf1),]) + rowf1names<-rownames(rowkeep1) + tabletotf2<-tabletot[order(tabletot[,2]),] + rowkeep2<-tabletotf2[1:nb,] + rowkeep2<-rbind(rowkeep2,tabletotf2[(nrow(tabletotf2)-nb):nrow(tabletotf2),]) + rowf2names<-rownames(rowkeep2) + count<-0 + for (names in 1:length(rowf1names)) { + for (names2 in 1:length(rowf2names)) { + if (rowf1names[names]==rowf2names[names2]) { + rowkeep1<-rowkeep1[-(as.numeric(names)-count),] + count<-count+1 + } + } + } + rowkeep12<-rbind(rowkeep1,rowkeep2) + if (fnb>=3) { + count<-0 + row12names<-rownames(rowkeep12) + tabletotf3<-tabletot[order(tabletot[,3]),] + rowkeep3<-tabletotf1[1:nb,] + rowkeep3<-rbind(rowkeep3,tabletotf3[(nrow(tabletotf3)-nb):nrow(tabletotf3),]) + rowf3names<-rownames(rowkeep3) + for (names in 1:length(row12names)) { + for (names3 in 1:length(rowf3names)) { + if (rowf3names[names3]==row12names[names]) { + rowkeep12<-rowkeep12[-(as.numeric(names)-count),] + count<-count+1 + } + } + } + rowkeep123<-rbind(rowkeep12,rowkeep3) + rowkeep123 + } + else { + rowkeep12 } +} + +uncover<-function(tabletot,x) { + #tabletot=tableau des coordonnées + tabletotf1<-tabletot$table[order(tabletot$table[,x]),] + ref<-tabletotf1[1,] + distminx<-0.4 + distminy<-0.2 + i<-2 + #k=nrow(elim) + if (is.null(tabletot$elim)){ + elim<-NULL#tabletot#$elim + } else { + elim=tabletot$elim + } + while (i < nrow(tabletotf1)) { + signex<-ref[1]*tabletotf1[i,1] + signey<-ref[2]*tabletotf1[i,2] + if (signex>0) { + dist<-abs(ref)-abs(tabletotf1[i,]) + dist<-abs(dist) + print(dist) + if (abs(dist[1])0){ + if (abs(dist[2])=3) { +# count<-0 +# row12names<-rownames(rowkeep12) +# tabletotf3<-tabletot[order(tabletot[,3]),] +# rowkeep3<-tabletotf1[1:nb,] +# rowkeep3<-rbind(rowkeep3,tabletotf3[(nrow(tabletotf3)-nb):nrow(tabletotf3),]) +# rowf3names<-rownames(rowkeep3) +# for (names in 1:length(row12names)) { +# for (names3 in 1:length(rowf3names)) { +# if (rowf3names[names3]==row12names[names]) { +# rowkeep12<-rowkeep12[-(as.numeric(names)-count),] +# count<-count+1 +# } +# } +# } +# rowkeep123<-rbind(rowkeep12,rowkeep3) +# rowkeep123 +# } +# else { +# rowkeep12 } +#} diff --git a/Rscripts/simi.R b/Rscripts/simi.R new file mode 100644 index 0000000..e738a80 --- /dev/null +++ b/Rscripts/simi.R @@ -0,0 +1,297 @@ +#from proxy package +############################################################# +#a, b, c, and d are the counts of all (TRUE, TRUE), (TRUE, FALSE), (FALSE, TRUE), and (FALSE, FALSE) +# n <- a + b + c + d = nrow(x) + +make.a <- function(x) { + a <- t(x) %*% (x) + a +} + +make.b <- function(x) { + b <- t(x) %*% (1 - x) + b +} + +make.c <- function(x) { + c <- (1-t(x)) %*% x + c +} + +make.d <- function(x, a, b, c) { +#??????????? ncol ? + d <- ncol(x) - a - b - c + d +} + +########################################### +#x, a +########################################### +my.jaccard <- function(x) { + a <- make.a(x) + b <- make.b(x) + c <- make.c(x) + d <- make.d(x, a, b, c) + jac <- a / (a + b + c) + jac +} + + +prcooc <- function(x, a) { + prc <- (a / nrow(x)) + prc +} + +make.bin <- function(cs, a, i, j, nb) { + if (a[i, j] >= 1) { + ab <- a[i, j] - 1 + res <- binom.test(ab, nb, (cs[i]/nb) * (cs[j]/nb), "less") + } else { + res <- NULL + res$p.value <- 0 + } + #res <- binom.test(ab, nb, (cs[i]/nb) * (cs[j]/nb), "less") + res$p.value + } + +binom.sim <- function(x) { + a <- make.a(x) + n <- nrow(x) + cs <- colSums(x) + mat <- matrix(0,ncol(x),ncol(x)) + colnames(mat)<-colnames(a) + rownames(mat)<-rownames(a) + for (i in 1:(ncol(x)-1)) { + for (j in (i+1):ncol(x)) { + mat[j,i] <- make.bin(cs, a, i, j , n) + } + } +# print(mat) + mat +} + + +############################################ +# a, b, c +############################################ +# jaccard a, b, c a / (a + b + c) +# Kulczynski1 a, b, c a / (b + c) +# Kulczynski2 a, b, c [a / (a + b) + a / (a + c)] / 2 +# Mountford a, b, c 2a / (ab + ac + 2bc) +# Fager, McGowan a, b, c a / sqrt((a + b)(a + c)) - 1 / 2 sqrt(a + c) +# Russel, Rao a (a/n) +# Dice, Czekanowski, Sorensen a, b, c 2a / (2a + b + c) +# Mozley, Margalef a, b, c an / (a + b)(a + c) +# Ochiai a, b, c a / sqrt[(a + b)(a + c)] +# Simpson a, b, c a / min{(a + b), (a + c)} +# Braun-Blanquet a, b, c a / max{(a + b), (a + c)} + +#simple matching, Sokal/Michener a, b, c, d, ((a + d) /n) +# Hamman, a, b, c, d, ([a + d] - [b + c]) / n +# Faith , a, b, c, d, (a + d/2) / n +# Tanimoto, Rogers a, b, c, d, (a + d) / (a + 2b + 2c + d) +# Phi a, b, c, d (ad - bc) / sqrt[(a + b)(c + d)(a + c)(b + d)] +# Stiles a, b, c, d log(n(|ad-bc| - 0.5n)^2 / [(a + b)(c + d)(a + c)(b + d)]) +# Michael a, b, c, d 4(ad - bc) / [(a + d)^2 + (b + c)^2] +# Yule a, b, c, d (ad - bc) / (ad + bc) +# Yule2 a, b, c, d (sqrt(ad) - sqrt(bc)) / (sqrt(ad) + sqrt(bc)) + +BuildProf01<-function(x,classes) { + #x : donnees en 0/1 + #classes : classes de chaque lignes de x + dm<-cbind(x,cl=classes) + clnb=length(summary(as.data.frame(as.character(classes)),max=100)) + print(clnb) + print(summary(as.data.frame(as.character(classes)),max=100)) + mat<-matrix(0,ncol(x),clnb) + rownames(mat)<-colnames(x) + for (i in 1:clnb) { + dtmp<-dm[which(dm$cl==i),] + for (j in 1:(ncol(dtmp)-1)) { + mat[j,i]<-sum(dtmp[,j]) + } + } + mat +} + +do.simi <- function(x, method = 'cooc',seuil = NULL, p.type = 'tkplot',layout.type = 'frutch', max.tree = TRUE, coeff.vertex=NULL, coeff.edge = NULL, minmaxeff=c(NULL,NULL), vcexminmax= c(NULL,NULL), cex = 1, coords = NULL) { + mat.simi <- x$mat + mat.eff <- x$eff + v.label <- colnames(mat.simi) + g1<-graph.adjacency(mat.simi,mode="lower",weighted=TRUE) + g.toplot<-g1 + weori<-get.edge.attribute(g1,'weight') + if (max.tree) { + invw<-1/weori + E(g1)$weight<-invw + g.max<-minimum.spanning.tree(g1) + E(g.max)$weight<-1/E(g.max)$weight + g.toplot<-g.max + } + + if (!is.null(seuil)) { + if (seuil >= max(mat.simi)) seuil <- max(mat.simi)-1 + vec<-vector() + w<-E(g.toplot)$weight + tovire <- which(w<=seuil) + g.toplot <- delete.edges(g.toplot,(tovire-1)) + for (i in 0:(length(V(g.toplot))-1)) { + if (length(neighbors(g.toplot,i))==0) { + vec<-append(vec,i) + } + } + g.toplot <- delete.vertices(g.toplot,vec) + v.label <- V(g.toplot)$name + if (!is.logical(vec)) mat.eff <- mat.eff[-(vec+1)] + } + + if (!is.null(minmaxeff[1])) { + eff<-norm.vec(mat.eff,minmaxeff[1],minmaxeff[2]) + } else { + eff<-coeff.vertex + } + if (!is.null(vcexminmax[1])) { + label.cex = norm.vec(mat.eff, vcexminmax[1], vcexminmax[2]) + } else { + label.cex = cex + } + if (!is.null(coeff.edge)) { + we.width <- norm.vec(abs(E(g.toplot)$weight), coeff.edge[1], coeff.edge[2]) + #we.width <- abs((E(g.toplot)$weight/max(abs(E(g.toplot)$weight)))*coeff.edge) + } else { + we.width <- NULL + } + if (method != 'binom') { + we.label <- round(E(g.toplot)$weight,1) + } else { + we.label <- round(E(g.toplot)$weight,3) + } + if (p.type=='rgl') { + nd<-3 + } else { + nd<-2 + } + if (is.null(coords)) { + if (layout.type == 'frutch') + lo <- layout.fruchterman.reingold(g.toplot,dim=nd)#, weightsA=E(g.toplot)$weight) + if (layout.type == 'kawa') + lo <- layout.kamada.kawai(g.toplot,dim=nd) + if (layout.type == 'random') + lo <- layout.random(g.toplot,dim=nd) + if (layout.type == 'circle' & p.type != 'rgl') + lo <- layout.circle(g.toplot) + if (layout.type == 'circle' & p.type == 'rgl') + lo <- layout.sphere(g.toplot) + if (layout.type == 'graphopt') + lo <- layout.graphopt(g.toplot) + } else { + lo <- coords + } + out <- list(graph = g.toplot, mat.eff = mat.eff, eff = eff, mat = mat.simi, v.label = v.label, we.width = we.width, we.label=we.label, label.cex = label.cex, layout = lo) +} + +plot.simi <- function(graph.simi, p.type = 'tkplot',filename=NULL, vertex.col = 'red', edge.col = 'black', edge.label = TRUE, vertex.label=TRUE, vertex.label.color = 'black', vertex.label.cex= NULL, vertex.size=NULL, leg=NULL, width = 800, height = 800, alpha = 0.1, cexalpha = FALSE, movie = NULL) { + mat.simi <- graph.simi$mat + g.toplot <- graph.simi$graph + if (is.null(vertex.size)) { + vertex.size <- graph.simi$eff + } else { + vertex.size <- vertex.size + } + we.width <- graph.simi$we.width + if (vertex.label) { + #v.label <- vire.nonascii(graph.simi$v.label) + v.label <- graph.simi$v.label + } else { + v.label <- NA + } + if (edge.label) { + we.label <- graph.simi$we.label + } else { + we.label <- NA + } + lo <- graph.simi$layout + if (!is.null(vertex.label.cex)) { + label.cex<-vertex.label.cex + } else { + label.cex = graph.simi$label.cex + } + if (cexalpha) { + alphas <- norm.vec(label.cex, 0.5,1) + nvlc <- NULL + if (length(vertex.label.color) == 1) { + for (i in 1:length(alphas)) { + nvlc <- append(nvlc, adjustcolor(vertex.label.color, alpha=alphas[i])) + } + } else { + for (i in 1:length(alphas)) { + nvlc <- append(nvlc, adjustcolor(vertex.label.color[i], alpha=alphas[i])) + } + } + vertex.label.color <- nvlc + } + if (p.type=='nplot') { + #print('ATTENTION - PAS OPEN FILE') + open_file_graph(filename, width = width, height = height) + par(mar=c(2,2,2,2)) + if (!is.null(leg)) { + layout(matrix(c(1,2),1,2, byrow=TRUE),widths=c(3,lcm(7))) + par(mar=c(2,2,1,0)) + } + par(pch=' ') + plot(g.toplot,vertex.label='', edge.width=we.width, vertex.size=vertex.size, vertex.color=vertex.col, vertex.label.color='white', edge.label=we.label, edge.label.cex=cex, edge.color=edge.col, vertex.label.cex = 0, layout=lo) + txt.layout <- layout.norm(lo, -1, 1, -1, 1, -1, 1) + #txt.layout <- txt.layout[order(label.cex),] + #vertex.label.color <- vertex.label.color[order(label.cex)] + #v.label <- v.label[order(label.cex)] + #label.cex <- label.cex[order(label.cex)] + text(txt.layout[,1], txt.layout[,2], v.label, cex=label.cex, col=vertex.label.color) + if (!is.null(leg)) { + par(mar=c(0,0,0,0)) + plot(0, axes = FALSE, pch = '') + legend(x = 'center' , leg$unetoile, fill = leg$gcol) + } + dev.off() + return(lo) + } + if (p.type=='tkplot') { + id <- tkplot(g.toplot,vertex.label=v.label, edge.width=we.width, vertex.size=vertex.size, vertex.color=vertex.col, vertex.label.color=vertex.label.color, edge.label=we.label, edge.color=edge.col, layout=lo) + coords = tkplot.getcoords(id) + ok <- try(coords <- tkplot.getcoords(id), TRUE) + while (is.matrix(ok)) { + ok <- try(coords <- tkplot.getcoords(id), TRUE) + Sys.sleep(0.5) + } + tkplot.off() + return(coords) + } + + if (p.type == 'rgl') { + library('rgl') + rglplot(g.toplot,vertex.label= vire.nonascii(v.label), edge.width=we.width/10, vertex.size=0.01, vertex.color=vertex.col, vertex.label.color="black", edge.color = edge.col, layout=lo) + los <- layout.norm(lo, -1, 1, -1, 1, -1, 1) + rgl.spheres(los, col = vertex.col, radius = vertex.size/100, alpha = alpha) + rgl.bg(color = c('white','black')) + if (!is.null(movie)) { + require(tcltk) + ReturnVal <- tkmessageBox(title="RGL 3 D",message="Cliquez pour commencer le film",icon="info",type="ok") + + movie3d(spin3d(axis=c(0,1,0),rpm=6), movie = 'film_graph', frames = "tmpfilm", duration=10, clean=TRUE, top = TRUE, dir = movie) + ReturnVal <- tkmessageBox(title="RGL 3 D",message="Film fini !",icon="info",type="ok") + } + #play3d(spin3d(axis=c(0,1,0),rpm=6)) + require(tcltk) + ReturnVal <- tkmessageBox(title="RGL 3 D",message="Cliquez pour fermer",icon="info",type="ok") + rgl.close() + # while (rgl.cur() != 0) + # Sys.sleep(0.5) + } +} + + +graph.word <- function(mat.simi, index) { + nm <- matrix(0, ncol = ncol(mat.simi), nrow=nrow(mat.simi), dimnames=list(row.names(mat.simi), colnames(mat.simi))) + nm[,index] <- mat.simi[,index] + nm[index,] <- mat.simi[index,] + nm +} diff --git a/Rscripts/simied.R b/Rscripts/simied.R new file mode 100644 index 0000000..6ac71cb --- /dev/null +++ b/Rscripts/simied.R @@ -0,0 +1,311 @@ +############################################################# +makesimi<-function(dm){ + a<-dm + m<-matrix(0,ncol(dm),ncol(dm)) + rownames(m)<-colnames(a) + colnames(m)<-colnames(a) + eff<-colSums(a) + for (col in 1:(ncol(a)-1)){ + for (colc in (col+1):ncol(a)){ + ta<-table(a[,col],a[,colc]) + if (ncol(ta)==1 & colnames(ta)[1]=='0') { + ta<-cbind(ta,'1'=c(0,0)) + } else if (ncol(ta)==1 & colnames(ta)[1]=='1') { + ta<-cbind('0'=c(0,0),t) + } else if (nrow(ta)==1 & rownames(ta)[1]=='0'){ + ta<-rbind(ta,'1'=c(0,0)) + } else if (nrow(ta)==1 & rownames(ta)[1]=='1') { + ta<-rbind('0'=c(0,0),ta) + } + #m[colc,col]<-length(which((a[,col]==1) & (a[,colc]==1))) + m[colc,col]<-ta[2,2] + m[col,colc]<-m[colc,col] + } + } + out<-list(mat=m,eff=eff) +} + + +makesimip<-function(dm){ + a<-dm + m<-matrix(0,ncol(dm),ncol(dm)) + rownames(m)<-colnames(a) + colnames(m)<-colnames(a) + eff<-colSums(a) + for (col in 1:(ncol(a)-1)){ + for (colc in (col+1):ncol(a)){ + ta<-table(a[,col],a[,colc]) + if (ncol(ta)==1 & colnames(ta)[1]=='0') { + ta<-cbind(ta,'1'=c(0,0)) + } else if (ncol(ta)==1 & colnames(ta)[1]=='1') { + ta<-cbind('0'=c(0,0),t) + } else if (nrow(ta)==1 & rownames(ta)[1]=='0'){ + ta<-rbind(ta,'1'=c(0,0)) + } else if (nrow(ta)==1 & rownames(ta)[1]=='1') { + ta<-rbind('0'=c(0,0),ta) + } + #m[colc,col]<-length(which((a[,col]==1) & (a[,colc]==1))) + m[colc,col]<-ta[2,2] + m[col,colc]<-m[colc,col] + } + } + m<-round((m/nrow(a))*100,digits=0) + out<-list(mat=m,eff=eff) +} + + +makejac<-function(dm){ + a<-dm + m<-matrix(0,ncol(dm),ncol(dm)) + rownames(m)<-colnames(a) + colnames(m)<-colnames(a) + eff<-colSums(a) + for (col in 1:(ncol(a)-1)){ + for (colc in (col+1):ncol(a)){ + ta<-table(a[,col],a[,colc]) + if (ncol(ta)==1 & colnames(ta)[1]=='0') { + ta<-cbind(ta,'1'=c(0,0)) + } else if (ncol(ta)==1 & colnames(ta)[1]=='1') { + ta<-cbind('0'=c(0,0),t) + } else if (nrow(ta)==1 & rownames(ta)[1]=='0'){ + ta<-rbind(ta,'1'=c(0,0)) + } else if (nrow(ta)==1 & rownames(ta)[1]=='1') { + ta<-rbind('0'=c(0,0),ta) + } + m[colc,col]<-(ta[2,2]/(ta[1,2]+ta[2,1]+ta[2,2]))*100 + #m[colc,col]<-(length(which((a[,col]==1) & (a[,colc]==1)))/(eff[col]+eff[colc]-length(which((a[,col]==1) & (a[,colc]==1)))))*100 + m[col,colc]<-m[colc,col] + } + } + out<-list(mat=m,eff=eff) +} + +makesimipond<-function(dm) { + a<-dm + m<-matrix(0,ncol(dm),ncol(dm)) + rownames(m)<-colnames(dm) + colnames(m)<-colnames(dm) + eff<-colSums(a) + #a<-t(a) + #print(a) + #lt<-list() + for (col in 1:(ncol(a)-1)){ + for (colc in (col+1):ncol(a)){ + m[colc,col]<-length(which((a[,col]>1) & (a[,colc]>1))) + m[col,colc]<-m[colc,col] + } + } + out<-list(mat=m,eff=eff) +} + +BuildProf01<-function(x,classes) { + #x : donnees en 0/1 + #classes : classes de chaque lignes de x + dm<-cbind(x,cl=classes) + clnb=length(summary(as.data.frame(as.character(classes)),max=100)) + print(clnb) + print(summary(as.data.frame(as.character(classes)),max=100)) + mat<-matrix(0,ncol(x),clnb) + #print(mat) + rownames(mat)<-colnames(x) + for (i in 1:clnb) { + dtmp<-dm[which(dm$cl==i),] + for (j in 1:(ncol(dtmp)-1)) { + #print(rownames(dtmp[j,])) + mat[j,i]<-sum(dtmp[,j]) + } + } + mat +} +################################################################### + +source('/home/pierre/workspace/iramuteq/Rscripts/chdfunct.R') + +#} +print('lecture') + + +##################################################################### +#suede enfance en danger +#ed<-read.csv2('/home/pierre/fac/suede/resultats/enfance_en_danger01.csv') +#coop<-read.csv2('/home/pierre/fac/suede/resultats/cooperation01.csv') +#as<-read.csv2('/home/pierre/fac/suede/resultats/as01.csv') +#ed<-ed[,-ncol(ed)] +#coop<-coop[,-ncol(coop)] +#as<-as[,-ncol(as)] +##tot<-tot[,-ncol(tot)] +#tot<-cbind(ed,coop) +#tot<-cbind(tot,as) +#tot<-as +# +#tot<-read.csv2('/home/pierre/fac/suede/resultats/as_catper01.csv',row.names=1) +##tot<-read.csv2('/home/pierre/fac/suede/resultats/swedish_as601.csv',row.names=1) +##print(tot) +#tot<-tot[,-ncol(tot)] +#pn<-tot[nrow(tot),] +#tot<-tot[-nrow(tot),] +#prof<-BuildProf01(tab,grp[,1]) +#outp<-AsLexico(prof) +#chistabletot<-outp[[2]] + +########################################################## +#SUP +ens<-read.csv2('/home/pierre/fac/SUP/resultats/enseignant01.csv',row.names=1) +vp<-read.csv2('/home/pierre/fac/SUP/resultats/vieprivee01.csv',row.names=1) +tt<-read.csv2('/home/pierre/fac/SUP/resultats/asso_ens_vp01.csv',row.names=1) +ens<-ens[,-ncol(ens)] +vp<-vp[,-ncol(vp)] +tot<-tt[,-ncol(tt)] +print(nrow(tot)) +gr<-vector(mode='integer',length=nrow(tot)) +gr[1:41]<-1 +gr[42:82]<-2 + +mat<-BuildProf01(tot,cl=gr) +prof<-AsLexico(mat) +chis<-prof[[2]] +print(chis) +#tot<-tot[,-ncol(tot)] +#print(tot) +#as<-vector(mode='integer',length=ncol(tot)) +#as[1:ncol(ens)]<-'red' +#as[ncol(ens)+1:ncol(tot)]<-'green' +#print('liste des classes') +#listclasse<-vector(mode='integer',length=ncol(tab)) +for (line in 1:nrow(chistabletot)) { + if (max(chistabletot[line,])>2) { + listclasse[line]<-as.vector(which.max(chistabletot[line,])) + } else { + listclasse[line]<-3 + } +} +#classes<-listclasse +#classes<-classes[1:cag] +#print(classes) +#tot<-cbind(info,biblio) +#tot<-cbind(tot,rechdoc) + + + +print('matrice de similitude') +mindus<-makesimip(tot) +m<-mindus$m +eff<-mindus$eff + +cn<-paste(colnames(m),eff,sep=' ') +#mateff<-makesimipond(ministre) +#mateff<-makesimi(tot) +#mateff<-makejac(tot) +#m<-mateff$mat +#eff<-mateff$eff +#m<-as.matrix(dist(t(ministre),method='binary',upper=TRUE, diag=TRUE)) +#m<-as.matrix(simil(ministre), method='Jaccard', upper=TRUE, diag=TRUE, by_rows=FALSE) +#print(nrow(m)) +#print(ncol(m)) +#print(length(colnames(ministre))) +#colnames(m)<-colnames(ministre) +#rownames(m)<-colnames(ministre) +#eff<-colSums(ministre) +#mateffp<-makesimi(ministre) +#mateffp<-makesimipond(ministre) +#m<-mateffp$mat +#eff<-mateffp$eff + +#matave<-makesimi(ministre) +#m<-matave$m +#eff<-matave$eff + +print('couleur') +#rain<-rainbow(3) +#append(rain,'black') +rain<-c("green","red","white","blue")#"yellow","pink","black")#green","blue","red","black") +vcol<-vector(mode='integer',length=length(eff)) +vcolb<-vector(mode='integer',length=length(eff)) +#classes<-classes[1:93] +#classes<-grp +for (i in 1:nrow(chis)) { + vcolb[i]<-which.max(chis[i,]) +} +print(vcolb) +for (i in 1:length(eff)){ + #if (as.integer(pn[i])==0){ + # pn[i]<-4 + # print('zero') + #} + #vcol[i]<-rain[as.integer(pn[i])] + vcol[i]<-rain[as.integer(vcolb[i])] + } +#ll<-which(effp>=0.5) +#for (i in which(effp>=0.5)) { +# if (i<=10) { +# vcol[i]<-"blue" +# } else { +# vcol[i]<-"pink" +# } +#} +#print('length(vcol)') +#print(length(vcol)) +#vcol[1:cchaud]<-rain[1] +#vcol[(cchaud+1):(cchaud+cop)]<-rain[2] +#vcol[(cchaud+cop+1):length(eff)]<-rain[3] +#print(classes) +print('premier graph') +library(igraph) +#sink('graph.txt') +g1<-graph.adjacency(m,mode="lower",weighted=TRUE) + +#maxtree<-maxgraph(g1) +#plot(maxtree, layout=layout.circle) +#lo<-layout.circle(g1) +eff<-(eff/max(eff)) +weori<-get.edge.attribute(g1,'weight') +#tdel<-which(weori<3) +we<-(weori/max(weori))*4 +print('arbre maximum') +invw<-1/weori +E(g1)$weight<-invw +g3<-minimum.spanning.tree(g1) +E(g3)$weight<-1/E(g3)$weight +#print(E(g3)$weight) +#sink() +#g3<-g1 +wev<-eff*30 +wee<-(E(g3)$weight/max(E(g3)$weight))*10 +weori<-E(g3)$weight +print('layout') +#lo<-layout.kamada.kawai(g3,dim=3) +lo<-layout.fruchterman.reingold(g3,dim=3) +print('lo') +#print(nrow(lo)) +#lo<-layout.sphere(g3) +#lo<-cbind(lo,eff) +vsize<-vector(mode='integer',length=nrow(lo)) +#print(we) +#ecount(g1) +#g2<-simplify(g1) +#g2<-delete.edges(g2,tdel-1) +#tmax<-clusters(g1) +#print(tmax) +#we<-we[-tdel] +#weori<-weori[-tdel] +#plot(g1,vertex.label=colnames(m),edge.width=get.edge.attribute(g1,'weight'),layout=layout.circle,vertex.shape='none') +#igraph.par('print.edge.attributes',TRUE) +#plot(g2,vertex.label=colnames(m),vertex.size=vsize,vertex.color=vcol,edge.width=we,layout=lo)#,vertex.shape='none')#,edge.label=weori) +#rglplot(g3,vertex.label=colnames(m),edge.width=we,vertex.size=vsize,vertex.label.color=vcol,layout=lo,vertex.shape='none')#,edge.label=weori) +print('plot') +tkplot(g3,vertex.label=cn,edge.width=wee,vertex.size=wev,vertex.color=vcol,vertex.label.color="black",edge.label=weori,layout=lo)#,vcolb vertex.label.dist=1)#,vertex.shape='none')#,edge.label=weori) +#},vertex.color=vcol +#rgl.bg(sphere=FALSE,color=c("black","white")) +#vertex.color=vcol,vertex.label.color=vcol,edge.label=weori, +#rgl.viewpoint(zoom=0.6) +#movie3d(spin3d(axis=c(0,1,0),rpm=6),10,dir='/home/pierre/workspace/iramuteq/corpus/',movie="vero_cooc_explo",clean=TRUE,convert=TRUE,fps=20) +#tkplot(g1,vertex.label=colnames(m),layout=layout.circle,vertex.shape='rectangle') +#rglplot(g1,vertex.label=colnames(m),layout=layout.circle) +#g1<-graph(m) +#tkplot(g1) + + + + + +#Ministre diff --git a/Rscripts/simiold.R b/Rscripts/simiold.R new file mode 100644 index 0000000..918d3ca --- /dev/null +++ b/Rscripts/simiold.R @@ -0,0 +1,502 @@ +############################################################# +makesimi<-function(dm){ + a<-dm + m<-matrix(0,ncol(dm),ncol(dm)) + rownames(m)<-colnames(a) + colnames(m)<-colnames(a) + eff<-colSums(a) + for (col in 1:(ncol(a)-1)){ + for (colc in (col+1):ncol(a)){ + ta<-table(a[,col],a[,colc]) + if (ncol(ta)==1 & colnames(ta)[1]=='0') { + ta<-cbind(ta,'1'=c(0,0)) + } else if (ncol(ta)==1 & colnames(ta)[1]=='1') { + ta<-cbind('0'=c(0,0),t) + } else if (nrow(ta)==1 & rownames(ta)[1]=='0'){ + ta<-rbind(ta,'1'=c(0,0)) + } else if (nrow(ta)==1 & rownames(ta)[1]=='1') { + ta<-rbind('0'=c(0,0),ta) + } + #m[colc,col]<-length(which((a[,col]==1) & (a[,colc]==1))) + m[colc,col]<-ta[2,2] + m[col,colc]<-m[colc,col] + } + } + out<-list(mat=m,eff=eff) +} + +makejac<-function(dm){ + a<-dm + m<-matrix(0,ncol(dm),ncol(dm)) + rownames(m)<-colnames(a) + colnames(m)<-colnames(a) + eff<-colSums(a) + for (col in 1:(ncol(a)-1)){ + for (colc in (col+1):ncol(a)){ + ta<-table(a[,col],a[,colc]) + if (ncol(ta)==1 & colnames(ta)[1]=='0') { + ta<-cbind(ta,'1'=c(0,0)) + } else if (ncol(ta)==1 & colnames(ta)[1]=='1') { + ta<-cbind('0'=c(0,0),t) + } else if (nrow(ta)==1 & rownames(ta)[1]=='0'){ + ta<-rbind(ta,'1'=c(0,0)) + } else if (nrow(ta)==1 & rownames(ta)[1]=='1') { + ta<-rbind('0'=c(0,0),ta) + } + m[colc,col]<-(ta[2,2]/(ta[1,2]+ta[2,1]+ta[2,2]))*100 + #m[colc,col]<-(length(which((a[,col]==1) & (a[,colc]==1)))/(eff[col]+eff[colc]-length(which((a[,col]==1) & (a[,colc]==1)))))*100 + m[col,colc]<-m[colc,col] + } + } + out<-list(mat=m,eff=eff) +} + +makesimipond<-function(dm) { + a<-dm + m<-matrix(0,ncol(dm),ncol(dm)) + rownames(m)<-colnames(dm) + colnames(m)<-colnames(dm) + eff<-colSums(a) + #a<-t(a) + #print(a) + #lt<-list() + for (col in 1:(ncol(a)-1)){ + for (colc in (col+1):ncol(a)){ + m[colc,col]<-length(which((a[,col]>1) & (a[,colc]>1))) + m[col,colc]<-m[colc,col] + } + } + out<-list(mat=m,eff=eff) +} + +BuildProf01<-function(x,classes) { + #x : donnees en 0/1 + #classes : classes de chaque lignes de x + dm<-cbind(x,cl=classes) + clnb=length(summary(as.data.frame(as.character(classes)),max=100)) + print(clnb) + print(summary(as.data.frame(as.character(classes)),max=100)) + mat<-matrix(0,ncol(x),clnb) + #print(mat) + rownames(mat)<-colnames(x) + for (i in 1:clnb) { + dtmp<-dm[which(dm$cl==i),] + for (j in 1:(ncol(dtmp)-1)) { + #print(rownames(dtmp[j,])) + mat[j,i]<-sum(dtmp[,j]) + } + } + mat +} +################################################################### + +source('/home/pierre/workspace/iramuteq/Rscripts/chdfunct.R') +#info<-read.csv2(file('/home/pierre/fac/etudiant/crepin/simi/info01.csv',encoding='latin1'),row.names=1) +#cinf<-ncol(info) +#biblio<-read.csv2(file('/home/pierre/fac/etudiant/crepin/simi/biblio01.csv',encoding='latin1'),row.names=1) +#cbib<-ncol(biblio) +#rechdoc<-read.csv2(file('/home/pierre/fac/etudiant/crepin/simi/recherdoc01.csv',encoding='latin1'),row.names=1) +#crech<-ncol(rechdoc) + +######################@@ +#MV +######################### +#chaud<-read.csv2('/home/pierre/fac/etudiant/sab/Chaudronnier_pour_MV01.csv',row.names=1) +##cchaud<-ncol(chaud) +#op<-read.csv2('/home/pierre/fac/etudiant/sab/ocn_pour_mv01.csv',row.names=1) +##cop<-ncol(op) +#soud<-read.csv2('/home/pierre/fac/etudiant/sab/soudeur_pour_mv01.csv',row.names=1) +##csoud<-ncol(soud) +#indus<-read.csv2('/home/pierre/fac/etudiant/sab/metier_indus01.csv',row.names=1) +##cmindus<-ncol(mindus) +#infor<-read.csv2('/home/pierre/fac/etudiant/sab/Informaticien_pour_MV01.csv',row.names=1) +##cinfor<-ncol(infor) +#trav<-read.csv2('/home/pierre/fac/etudiant/sab/travail_pour_mv01.csv',row.names=1) +##ctrav<-ncol(trav) +#tot<-cbind(chaud,op) +#tot<-cbind(tot,soud) +#tot<-cbind(tot,indus) +## +#list_data<-list('a'=chaud, 'b'=soud,'c'=infor,'d'=trav) +#tot<-cbind(chaud,op) +#tot<-chaud +##tot<-cbind(tot,infor) +##tot<-cbind(tot,trav) +#mv<-read.csv2('/home/pierre/fac/etudiant/sab/chaud_soud_opn_metierindus01.csv') +#mv<-mv[,-ncol(mv)] +#grp<-read.csv2('/home/pierre/fac/etudiant/sab/grp2.csv') +#tab<-tot + +print('passe ici') +#tot<-mv[,-ncol(mv)] +#tot<-trav +################################################# +#AGIR +#ag<-read.csv2('/home/pierre/workspace/iramuteq/corpus/tableau_agir2_recod_alc_entree_pourpassage_temp_AlcesteQuest_1/Act01.csv') +#cag<-ncol(ag) +#load('/home/pierre/workspace/iramuteq/corpus/tableau_agir2_recod_alc_entree_pourpassage_temp_AlcesteQuest_1/RData.RData') + +############################################################### +#Vero +#tot<-read.csv2('/home/pierre/fac/etudiant/crepin/simi/simitot01.csv') +#grp<-vector(mode='integer',length=ncol(tot)) +#grp[1:54]<-1 +#grp[55:108]<-2 +#grp[109:162]<-3 +#prof<-BuildProf01(tot,grp[,1]) +##prof<-prof[-nrow(prof),] +#outp<-AsLexico(prof) +#chistabletot<-outp[[2]] + +################################################################### +#Steph + +#tot<-read.csv2('/home/pierre/fac/etudiant/netto/simi-27-03-09/prof_inf_ecole_priv01.csv') +#tot<-tot[,-ncol(tot)] +#grp<-vector(mode='integer',length=nrow(tot)) +#grp[1:245]<-1 +#grp[246:490]<-2 +#prof<-BuildProf01(tot,grp) +#print(nrow(prof)) +##print(prof) +#outp<-AsLexico(prof) +#chistabletot<-outp[[2]] + +#tot<-read.csv2('/home/pierre/fac/etudiant/netto/simi-27-03-09/etu_inf_pro_priv01.csv') +#print(tot[,ncol(tot)]) +#tot<-tot[,-ncol(tot)] +#grp<-vector(mode='integer',length=nrow(tot)) +#grp[1:175]<-1 +#grp[176:350]<-2 +#prof<-BuildProf01(tot,grp) +#outp<-AsLexico(prof) +#chistabletot<-outp[[2]] + +#tot<-read.csv2('/home/pierre/fac/etudiant/netto/simi-27-03-09/prof_etu_inf_pro01.csv') +#print(tot[,ncol(tot)]) +#tot<-tot[,-ncol(tot)] +#grp<-vector(mode='integer',length=nrow(tot)) +#grp[1:245]<-1 +#grp[246:419]<-2 +#prof<-BuildProf01(tot,grp) +#outp<-AsLexico(prof) +#chistabletot<-outp[[2]] + +####################################################### +#grp ideal 2008 +#tot<-read.csv2('/home/pierre/workspace/iramuteq/corpus/grpiedal2008_01.csv') +#print(tot[,ncol(tot)]) +#grp<-vector(mode='integer',length=ncol(tot)) +#grp[1:10]<-1 +#grp[11:20]<-2 +#prof<-BuildProf01(tot,grp) +#outp<-AsLexico(prof) +#chistabletot<-outp[[2]] + +#ministre<-read.csv2('/home/pierre/workspace/iramuteq/corpus/Ministres_AFCUCI_3/TableCont.csv',header=TRUE) +#eff<-colSums(ministre) +#ministre<-ministre[,-which(eff<100)] +# +##ministre<-(ministre/colSums(ministre))*1000 +#print(ncol(ministre)) +#pm<-t(ministre) +#outp<-AsLexico(pm) +#chistabletot<-outp[[2]] +#ministre<-read.csv2('/home/pierre/workspace/iramuteq/corpus/Ministres_Alceste_1/TableUc1.csv',header=TRUE) +#load('/home/pierre/workspace/iramuteq/corpus/Ministres_Alceste_1/RData.RData') +#ministre<-ministre[,-which(eff<200)] +#uce<-read.csv2('/home/pierre/workspace/iramuteq/corpus/Ministres_Alceste_1/listeUCE1.csv') +#uc<-matrix(0,nrow(ministre),1) +#uc<-list() +#print(uce) +#print(nrow(n1)) +#for (i in 1:nrow(uce)) { +# uc[[as.integer(uce[i,2])+1]]<-n1[i,ncol(n1)] +#} +#print(nrow(ministre)) +#grp<-unlist(uc) +#prof<-BuildProf01(ag,n1[,ncol(n1)]) +#outp<-AsLexico(prof) +#chistabletot<-outp[[2]] + +####################### +#Steph +######################### +#priv<-read.csv2('/home/pierre/workspace/iramuteq/corpus/simi/info_priv01.csv') +#pro<-read.csv2('/home/pierre/workspace/iramuteq/corpus/simi/info_pro01.csv') +#cpriv<-ncol(priv) +#tot<-cbind(priv,pro) +#tot<-read.csv2('/home/pierre/workspace/iramuteq/corpus/simi/priv_pro_ens01.csv') +#print('tot') +#print(ncol(tot)) + + +#grp<-vector(mode='integer',length=nrow(tot)) +#grp[1:245]<-1 +#grp[246:nrow(tot)]<-2 +#grp[491:635]<-3 +#prof<-BuildProf01(tot,grp) +#prof<-prof[,-4] +#print(prof) +#print(nrow(prof)) +#outp<-AsLexico(prof) +#chistabletot<-outp[[2]] +#cs<-colSums(prof) +#prof<-prof/cs +#l<-vector(mode='integer',length=nrow(prof)) +#for (line in 1:nrow(prof)) { +# l[line]<-as.integer(which.max(prof[line,1:2])) +#} +print('lecture') +#chistabletot<-chistabletot[1:ncol(ministre),] +#print(nrow(chistabletot)) +#sc<-colSums(as.matrix(ministre)) +#print(length(sc)) +#outc<-which(sc>0) +#print(length(outc)) +#ministre<-ministre[,outc] +#print(ncol(ministre)) +#prof<-BuildProf01(ministre,grp) +#outp<-AsLexico(prof) +#chistabletot<-outp[[2]] +#chistabletot<-chistabletot[outc,] +#print(nrow(chistabletot)) +#ave<-read.csv2('/home/pierre/workspace/iramuteq/corpus/Avenir_Alceste_1/TableUc1.csv',header=TRUE) +#load('/home/pierre/workspace/iramuteq/corpus/Avenir_Alceste_1/RData.RData') +#chistabletot<-chistabletot[1:cag,] + +#print(grp) +#grp<-as.character(grp[,1]) +#titre<-c("chaudronnier","soudeur","informaticien","travail") +#count<-0 +#split.screen(c(2,2)) +#for (tab in list_data) { +#count<-count+1 +#screen(count) +#par(cex=0.7) +#grp<-rbind(grp,grp) +#grp<-rbind(grp,grp) +#print(ncol(tab)) + +##################################################################### +#suede enfance en danger +tot<-read.csv2('/home/pierre/fac/suede/resultats/cat_asso1_01.csv',row.names=1) + +#prof<-BuildProf01(tab,grp[,1]) +#outp<-AsLexico(prof) +#chistabletot<-outp[[2]] + +#print('liste des classes') +#listclasse<-vector(mode='integer',length=ncol(tab)) +#for (line in 1:nrow(chistabletot)) { +# if (max(chistabletot[line,])>0) { +# listclasse[line]<-as.vector(which.max(chistabletot[line,])) +# } else { +# listclasse[line]<-3 +# } +#} +#classes<-listclasse +#classes<-classes[1:cag] +#print(classes) +#tot<-cbind(info,biblio) +#tot<-cbind(tot,rechdoc) +listclasse<-vector(mode='integer',length=ncol(dt)) +for (line in 1:nrow(chistabletot)) { + if ((max(chistabletot[line,])>3.84) || (min(prof[line,])==0)) { + if (max(chistabletot[line,])>3.84) { + listclasse[line]<-as.vector(which.max(chistabletot[line,])) + } + if (min(prof[line,])==0) { + print('zero') + listclasse[line]<-as.vector(which.max(prof[line,])) + } + } else { + listclasse[line]<-3 + } +} + +print('matrice de similitude') +mindus<-makesimi(tot) +m<-mindus$m +eff<-mindus$eff + +#mateff<-makesimipond(ministre) +#mateff<-makesimi(tot) +#mateff<-makejac(tot) +#m<-mateff$mat +#eff<-mateff$eff +#m<-as.matrix(dist(t(ministre),method='binary',upper=TRUE, diag=TRUE)) +#m<-as.matrix(simil(ministre), method='Jaccard', upper=TRUE, diag=TRUE, by_rows=FALSE) +#print(nrow(m)) +#print(ncol(m)) +#print(length(colnames(ministre))) +#colnames(m)<-colnames(ministre) +#rownames(m)<-colnames(ministre) +#eff<-colSums(ministre) +#mateffp<-makesimi(ministre) +#mateffp<-makesimipond(ministre) +#m<-mateffp$mat +#eff<-mateffp$eff + +#matave<-makesimi(ministre) +#m<-matave$m +#eff<-matave$eff + +print('couleur') +#rain<-rainbow(3) +#append(rain,'black') +rain<-c("green","red","white")#"yellow","pink","black")#green","blue","red","black") +vcol<-vector(mode='integer',length=length(eff)) +vcolb<-vector(mode='integer',length=length(eff)) +#classes<-classes[1:93] +#classes<-grp +#for (i in 1:length(eff)){ +# vcol[i]<-rain[as.integer(classes[i])] +# vcolb[i]<-"black" +#} +#ll<-which(effp>=0.5) +#for (i in which(effp>=0.5)) { +# if (i<=10) { +# vcol[i]<-"blue" +# } else { +# vcol[i]<-"pink" +# } +#} +#print('length(vcol)') +#print(length(vcol)) +#vcol[1:cchaud]<-rain[1] +#vcol[(cchaud+1):(cchaud+cop)]<-rain[2] +#vcol[(cchaud+cop+1):length(eff)]<-rain[3] +#print(classes) +print('premier graph') +library(igraph) +#sink('graph.txt') +g1<-graph.adjacency(m,mode="lower",weighted=TRUE) + +#maxtree<-maxgraph(g1) +#plot(maxtree, layout=layout.circle) +#lo<-layout.circle(g1) +eff<-(eff/max(eff)) +weori<-get.edge.attribute(g1,'weight') +#tdel<-which(weori<3) +we<-(weori/max(weori))*4 +print('arbre maximum') +invw<-1/weori +E(g1)$weight<-invw +g3<-minimum.spanning.tree(g1) +E(g3)$weight<-1/E(g3)$weight +#print(E(g3)$weight) +#sink() +#g3<-g1 +wev<-eff*20 +wee<-(E(g3)$weight/max(E(g3)$weight))*10 +weori<-E(g3)$weight +print('layout') +#lo<-layout.kamada.kawai(g3,dim=3) +lo<-layout.fruchterman.reingold(g3,dim=3) +print('lo') +#print(nrow(lo)) +#lo<-layout.sphere(g3) +#lo<-cbind(lo,eff) +vsize<-vector(mode='integer',length=nrow(lo)) +#print(we) +#ecount(g1) +#g2<-simplify(g1) +#g2<-delete.edges(g2,tdel-1) +#tmax<-clusters(g1) +#print(tmax) +#we<-we[-tdel] +#weori<-weori[-tdel] +#plot(g1,vertex.label=colnames(m),edge.width=get.edge.attribute(g1,'weight'),layout=layout.circle,vertex.shape='none') +#igraph.par('print.edge.attributes',TRUE) +#plot(g2,vertex.label=colnames(m),vertex.size=vsize,vertex.color=vcol,edge.width=we,layout=lo)#,vertex.shape='none')#,edge.label=weori) +#rglplot(g3,vertex.label=colnames(m),edge.width=we,vertex.size=vsize,vertex.label.color=vcol,layout=lo,vertex.shape='none')#,edge.label=weori) +print('plot') +tkplot(g3,vertex.label=colnames(m),edge.width=wee,vertex.size=wev,vertex.label.color="black",layout=lo)#,vcolb vertex.label.dist=1)#,vertex.shape='none')#,edge.label=weori) +#},vertex.color=vcol +#rgl.bg(sphere=FALSE,color=c("black","white")) +#vertex.color=vcol,vertex.label.color=vcol,edge.label=weori, +#rgl.viewpoint(zoom=0.6) +#movie3d(spin3d(axis=c(0,1,0),rpm=6),10,dir='/home/pierre/workspace/iramuteq/corpus/',movie="vero_cooc_explo",clean=TRUE,convert=TRUE,fps=20) +#tkplot(g1,vertex.label=colnames(m),layout=layout.circle,vertex.shape='rectangle') +#rglplot(g1,vertex.label=colnames(m),layout=layout.circle) +#g1<-graph(m) +#tkplot(g1) + + + + + +#Ministre +autre : +dm<-read.csv2('classe_mod.csv',row.names=1,header = FALSE) +load('RData.RData') +#------------------------------------------------------ +#selectionner +chimax<-as.matrix(apply(chistabletot,1,max)) +chimax<-as.matrix(chimax[,1][1:nrow(dm)]) +chimax<-cbind(chimax,1:nrow(dm)) +order_chi<-as.matrix(chimax[order(chimax[,1],decreasing = TRUE),]) +elim<-which(rownames(dm) == 'faire') +dm<-dm[-elim,] +dm<-dm[chimax[,2][1:300],] +#------------------------------------------------------- +limit<-nrow(dm) +distm<-dist(dm,diag=TRUE) +distm<-as.matrix(distm) +g1<-graph.adjacency(distm,mode='lower',weighted=TRUE) +g1<-minimum.spanning.tree(g1) +lo<-layout.kamada.kawai(g1,dim=3) +mc<-rainbow(ncol(chistabletot)) +chistabletot<-chistabletot[-elim,] +cc<-vector() +for (i in 1:limit) { + cc<-append(cc,which.max(chistabletot[i,])) +} +cc<-mc[cc] +mass<-rowSums(dm)/100 +rglplot(g1,vertex.label = rownames(dm),vertex.label.color=cc,vertex.color=cc,vertex.size = mass, layout=lo) + + + +autre : +dm<-read.csv2('enseignant_simi_opi.csv',row.names=1,na.string='') +mat<-matrix(0,ncol=ncol(dm),nrow=ncol(dm)) +for (i in 1:(ncol(dm)-1)) { + for (j in (i+1):ncol(dm)){ + tab<-table(dm[,i],dm[,j]) + chi<-chisq.test(tab) + mat[i,j]<-chi$statistic + mat[j,i]<-chi$statistic + } +} +mat<-ifelse(mat>3.84,mat,0) +mat<-ifelse(is.na(mat),0,mat) +mat<-ifelse(is.infinite(mat),0,mat) +cs<-colSums(mat) +tovire<-which(cs==0) +if (!is.integer(tovire)){ +mat<-mat[-tovire,] +mat<-mat[,-tovire] +} +cn<-colnames(dm) +if (!is.integer(tovire)) cn<-cn[-tovire] +g1<-graph.adjacency(mat,mode='lower',weighted=TRUE) +lo<-layout.fruchterman.reingold(g1,dim=2) +wei<-E(g1)$weight +plot(g1,vertex.label=cn,vertex.size=0.1,edge.width=wei,edge.label=round(wei,2),layout=lo) + + + +levels.n<-mj$levels.n +levelnames<-mj$levelnames +cn<-mj$colnames +count<-0 +for (j in 1:ncol(dm)) { + for (i in 1:levels.n[j]) { + count<-count+1 + print(paste(cn[j],'\\.',sep='')) + levelnames[count]<-gsub(paste(cn[j],'\\.',sep=''),'',levelnames[count]) + } +} + \ No newline at end of file diff --git a/Rscripts/splitafc.R b/Rscripts/splitafc.R new file mode 100644 index 0000000..da84b38 --- /dev/null +++ b/Rscripts/splitafc.R @@ -0,0 +1,77 @@ +#library(ca) +#datadm<-read.csv2(file('TableCont.csv', encoding='utf-8')) +#afc<-ca(datadm, nd=3) +plotqrtafc<- function(afc,x=1,y=2,supcol=NA,filename='afcdiv4_',width=480,height=480,quality=75,reso=200,ptsize=12,PARCEX=PARCEX) { + if (is.na(supcol)) { + noc<-afc$colnames + colcoord<-afc$colcoord + } + else { + noc<-afc$colnames[supcol[1]:supcol[2]] + colcoord<-afc$colcoord[supcol[1]:supcol[2],] + } + fiqrt<-data.frame() + seqrt<-data.frame() + foqrt<-data.frame() + thqrt<-data.frame() + for (i in 1:(nrow(colcoord))) { + if (colcoord[,x][i]<0) { + if (colcoord[,y][i]<0) { + foqrt[i,1]<-colcoord[i,x] + foqrt[i,2]<-colcoord[i,y] + foqrt[i,3]<-noc[i] + } + else { + fiqrt[i,1]<-colcoord[i,x] + fiqrt[i,2]<-colcoord[i,y] + fiqrt[i,3]<-noc[i] + } + } + else { + if (colcoord[,2][i]<0) { + thqrt[i,1]<-colcoord[i,x] + thqrt[i,2]<-colcoord[i,y] + thqrt[i,3]<-noc[i] + } + else { + seqrt[i,1]<-colcoord[i,x] + seqrt[i,2]<-colcoord[i,y] + seqrt[i,3]<-noc[i] + } + } + } + list_name<-c('hg','bg','hd','bd') + list_table<-list(fiqrt,foqrt,seqrt,thqrt) + for (i in 1:4) { + if (Sys.info()["sysname"]=='Darwin') { + width<-width/74.97 + height<-height/74.97 + print(list_name[i]) + quartz(file=paste(filename,x,y,list_name[i],'.jpeg',sep=''),type='jpeg',width=width,height=height)#,pointsize=5) + } else { + jpeg(paste(filename,x,y,list_name[i],'.jpeg',sep=''),quality=quality,pointsize=ptsize,res=reso,width=width,height=height) + } + par(cex=PARCEX) + #par(mar=c(0.01,0.01,0.01,0.01)) + plot(list_table[[i]][,1],list_table[[i]][,2],pch='') + text(list_table[[i]][,1],list_table[[i]][,2],list_table[[i]][,3]) + dev.off() + } +# jpeg(paste(filename,x,y,'bg','.jpeg',sep=''),quality=quality,pointsize=ptsize,res=reso,width=width,height=height) +# par(cex=PARCEX) +# plot(foqrt[,1],foqrt[,2],pch='') +# text(foqrt[,1],foqrt[,2],foqrt[,3]) +# dev.off() +# jpeg(paste(filename,x,y,'hd','.jpeg',sep=''),quality=quality,pointsize=ptsize,res=reso,width=width,height=height) +# par(cex=PARCEX) +# plot(seqrt[,1],seqrt[,2],pch='') +# text(seqrt[,1],seqrt[,2],seqrt[,3]) +# dev.off() +# jpeg(paste(filename,x,y,'bd','.jpeg',sep=''),quality=quality,pointsize=ptsize,res=reso,width=width,height=height) +# par(cex=PARCEX) +# plot(thqrt[,1],thqrt[,2],pch='') +# text(thqrt[,1],thqrt[,2],thqrt[,3]) +# dev.off() +} + +#plotqrtafc(afc,x=1,y=2,PARCEX=0.7,quality=100,ptsize=12,reso=200,width=1000,height=1000) diff --git a/TODO b/TODO new file mode 100644 index 0000000..261804c --- /dev/null +++ b/TODO @@ -0,0 +1,13 @@ +- système de template pour la présentation des résultats +- traitement texte : + - méthode alceste (intégrer le temps et les personnes) +- AFCM (mja et mjca) +- système de configuration +- anova +- correlation +- U de Mann Whitney +- autres tests non paramétriques +- regression linéaire ? +- intégration avec openoffice + + diff --git a/agw/__init__.py b/agw/__init__.py new file mode 100644 index 0000000..a2dcee6 --- /dev/null +++ b/agw/__init__.py @@ -0,0 +1,121 @@ +""" +This is the Advanced Generic Widgets package (AGW). It provides many +custom-drawn wxPython controls: some of them can be used as a replacement +of the platform native controls, others are simply an addition to the +already rich wxPython widgets set. + + +Description: + +AGW contains many different modules, listed below. Items labelled with +an asterisk were already present in `wx.lib` before: + +- AdvancedSplash: reproduces the behaviour of `wx.SplashScreen`, with more + advanced features like custom shapes and text animations; +- AquaButton: this is another custom-drawn button class which + *approximatively* mimics the behaviour of Aqua buttons on the Mac; +- AUI: a pure-Python implementation of `wx.aui`, with many bug fixes and + new features like HUD docking and L{AuiNotebook} tab arts; +- BalloonTip: allows you to display tooltips in a balloon style window + (actually a frame), similarly to the Windows XP balloon help; +- ButtonPanel (*): a panel with gradient background shading with the + possibility to add buttons and controls still respecting the gradient + background; +- CubeColourDialog: an alternative implementation of `wx.ColourDialog`, it + offers different functionalities like colour wheel and RGB cube; +- CustomTreeCtrl (*): mimics the behaviour of `wx.TreeCtrl`, with almost the + same base functionalities plus a bunch of enhancements and goodies; +- FlatMenu: as the name implies, it is a generic menu implementation, + offering the same `wx.MenuBar`/`wx.Menu`/`wx.ToolBar` capabilities and much more; +- FlatNotebook (*): a full implementation of the `wx.Notebook`, and designed + to be a drop-in replacement for `wx.Notebook` with enhanced capabilities; +- FloatSpin: this class implements a floating point spinctrl, cabable (in + theory) of handling infinite-precision floating point numbers; +- FoldPanelBar (*): a control that contains multiple panels that can be + expanded or collapsed a la Windows Explorer/Outlook command bars; +- FourWaySplitter: this is a layout manager which manages four children like + four panes in a window, similar to many CAD software interfaces; +- GenericMessageDialog: it is a possible replacement for the standard + `wx.MessageDialog`, with a fancier look and extended functionalities; +- GradientButton: another custom-drawn button class which mimics Windows CE + mobile gradient buttons, using a tri-vertex blended gradient background; +- HyperLinkCtrl (*): this widget acts line an hyper link in a typical browser; +- HyperTreeList: a class that mimics the behaviour of `wx.gizmos.TreeListCtrl`, + with almost the same base functionalities plus some more enhancements; +- KnobCtrl: a widget which lets the user select a numerical value by + rotating it, like a slider with a wheel shape; +- LabelBook and FlatImageBook: these are a quasi-full implementations of + `wx.ListBook`, with additional features; +- MultiDirDialog: it represents a possible replacement for `wx.DirDialog`, + with the additional ability of selecting multiple folders at once and a + fancier look; +- PeakMeter: this widget mimics the behaviour of LED equalizers that are + usually found in stereos and MP3 players; +- PersistentControls: widgets which automatically save their state + when they are destroyed and restore it when they are recreated, even during + another program invocation; +- PieCtrl and ProgressPie: these are simple classes that reproduce the + behavior of a pie chart, in a static or progress-gauge-like way; +- PyBusyInfo: constructs a busy info window and displays a message in it: + it is similar to `wx.BusyInfo`; +- PyCollapsiblePane: a pure Python implementation of the original wxWidgets + C++ code of `wx.CollapsiblePane`, with customizable buttons; +- PyGauge: a generic `wx.Gauge` implementation, it supports the determinate + mode functions as `wx.Gauge`; +- PyProgress: it is similar to `wx.ProgressDialog` in indeterminated mode, but + with a different gauge appearance and a different spinning behavior; +- RibbonBar: the RibbonBar library is a set of classes for writing a ribbon + user interface, similar to the user interface present in recent versions + of Microsoft Office; +- RulerCtrl: it implements a ruler window that can be placed on top, bottom, + left or right to any wxPython widget. It is somewhat similar to the rulers + you can find in text editors software; +- ShapedButton: this class tries to fill the lack of "custom shaped" controls + in wxPython. It can be used to build round buttons or elliptic buttons; +- SpeedMeter: this widget tries to reproduce the behavior of some car + controls (but not only), by creating an "angular" control; +- SuperToolTip: a class that mimics the behaviour of `wx.TipWindow` and + generic tooltips, with many features and highly customizable; +- ThumbnailCtrl: a widget that can be used to display a series of images + in a "thumbnail" format; it mimics, for example, the Windows Explorer + behavior when you select the "view thumbnails" option; +- ToasterBox: a cross-platform widget to make the creation of MSN-style + "toaster" popups easier; +- UltimateListCtrl: mimics the behaviour of `wx.ListCtrl`, with almost the same + base functionalities plus some more enhancements; +- ZoomBar: a class that *appoximatively* mimics the behaviour of the Mac Dock, + inside a `wx.Panel`. + + +Bugs and Limitations: many, patches and fixes welcome :-D + +See the demos for an example of what AGW can do, and on how to use it. + +Copyright: Andrea Gavana + +License: Same as the version of wxPython you are using it with. + +SVN for latest code: +http://svn.wxwidgets.org/viewvc/wx/wxPython/3rdParty/AGW/ + +Mailing List: +wxpython-users@lists.wxwidgets.org + +My personal web page: +http://xoomer.alice.it/infinity77 + +Please let me know if you are using AGW! + +You can contact me at: + +andrea.gavana@gmail.com +gavana@kpo.kz + +AGW version: 0.9.1 + +Last updated: 10 Mar 2011, 15.00 GMT + +""" + +__version__ = "0.9.1" +__author__ = "Andrea Gavana " diff --git a/agw/__init__.pyc b/agw/__init__.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9cf5c66741543369aeccc0240c25435f9e221200 GIT binary patch literal 6117 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 KfAqmePyPe*r(F&J literal 0 HcmV?d00001 diff --git a/agw/aui/__init__.py b/agw/aui/__init__.py new file mode 100644 index 0000000..e5f1abf --- /dev/null +++ b/agw/aui/__init__.py @@ -0,0 +1,290 @@ +""" +AUI is an Advanced User Interface library that aims to implement "cutting-edge" +interface usability and design features so developers can quickly and easily create +beautiful and usable application interfaces. + + +Vision and Design Principles +============================ + +AUI attempts to encapsulate the following aspects of the user interface: + +* **Frame Management**: Frame management provides the means to open, move and hide common + controls that are needed to interact with the document, and allow these configurations + to be saved into different perspectives and loaded at a later time. + +* **Toolbars**: Toolbars are a specialized subset of the frame management system and should + behave similarly to other docked components. However, they also require additional + functionality, such as "spring-loaded" rebar support, "chevron" buttons and end-user + customizability. + +* **Modeless Controls**: Modeless controls expose a tool palette or set of options that + float above the application content while allowing it to be accessed. Usually accessed + by the toolbar, these controls disappear when an option is selected, but may also be + "torn off" the toolbar into a floating frame of their own. + +* **Look and Feel**: Look and feel encompasses the way controls are drawn, both when shown + statically as well as when they are being moved. This aspect of user interface design + incorporates "special effects" such as transparent window dragging as well as frame animation. + +AUI adheres to the following principles: + +- Use native floating frames to obtain a native look and feel for all platforms; +- Use existing wxPython code where possible, such as sizer implementation for frame management; +- Use standard wxPython coding conventions. + + +Usage +===== + +The following example shows a simple implementation that uses L{AuiManager} to manage +three text controls in a frame window:: + + class MyFrame(wx.Frame): + + def __init__(self, parent, id=-1, title="AUI Test", pos=wx.DefaultPosition, + size=(800, 600), style=wx.DEFAULT_FRAME_STYLE): + + wx.Frame.__init__(self, parent, id, title, pos, size, style) + + self._mgr = aui.AuiManager() + + # notify AUI which frame to use + self._mgr.SetManagedWindow(self) + + # create several text controls + text1 = wx.TextCtrl(self, -1, "Pane 1 - sample text", + wx.DefaultPosition, wx.Size(200,150), + wx.NO_BORDER | wx.TE_MULTILINE) + + text2 = wx.TextCtrl(self, -1, "Pane 2 - sample text", + wx.DefaultPosition, wx.Size(200,150), + wx.NO_BORDER | wx.TE_MULTILINE) + + text3 = wx.TextCtrl(self, -1, "Main content window", + wx.DefaultPosition, wx.Size(200,150), + wx.NO_BORDER | wx.TE_MULTILINE) + + # add the panes to the manager + self._mgr.AddPane(text1, AuiPaneInfo().Left().Caption("Pane Number One")) + self._mgr.AddPane(text2, AuiPaneInfo().Bottom().Caption("Pane Number Two")) + self._mgr.AddPane(text3, AuiPaneInfo().CenterPane()) + + # tell the manager to "commit" all the changes just made + self._mgr.Update() + + self.Bind(wx.EVT_CLOSE, self.OnClose) + + + def OnClose(self, event): + + # deinitialize the frame manager + self._mgr.UnInit() + + self.Destroy() + event.Skip() + + + # our normal wxApp-derived class, as usual + + app = wx.PySimpleApp() + + frame = MyFrame(None) + app.SetTopWindow(frame) + frame.Show() + + app.MainLoop() + + +What's New +========== + +Current wxAUI Version Tracked: wxWidgets 2.9.0 (SVN HEAD) + +The wxPython AUI version fixes the following bugs or implement the following +missing features (the list is not exhaustive): + +- Visual Studio 2005 style docking: http://www.kirix.com/forums/viewtopic.php?f=16&t=596 +- Dock and Pane Resizing: http://www.kirix.com/forums/viewtopic.php?f=16&t=582 +- Patch concerning dock resizing: http://www.kirix.com/forums/viewtopic.php?f=16&t=610 +- Patch to effect wxAuiToolBar orientation switch: http://www.kirix.com/forums/viewtopic.php?f=16&t=641 +- AUI: Core dump when loading a perspective in wxGTK (MSW OK): http://www.kirix.com/forums/viewtopic.php?f=15&t=627 +- wxAuiNotebook reordered AdvanceSelection(): http://www.kirix.com/forums/viewtopic.php?f=16&t=617 +- Vertical Toolbar Docking Issue: http://www.kirix.com/forums/viewtopic.php?f=16&t=181 +- Patch to show the resize hint on mouse-down in aui: http://trac.wxwidgets.org/ticket/9612 +- The Left/Right and Top/Bottom Docks over draw each other: http://trac.wxwidgets.org/ticket/3516 +- MinSize() not honoured: http://trac.wxwidgets.org/ticket/3562 +- Layout problem with wxAUI: http://trac.wxwidgets.org/ticket/3597 +- Resizing children ignores current window size: http://trac.wxwidgets.org/ticket/3908 +- Resizing panes under Vista does not repaint background: http://trac.wxwidgets.org/ticket/4325 +- Resize sash resizes in response to click: http://trac.wxwidgets.org/ticket/4547 +- "Illegal" resizing of the AuiPane? (wxPython): http://trac.wxwidgets.org/ticket/4599 +- Floating wxAUIPane Resize Event doesn't update its position: http://trac.wxwidgets.org/ticket/9773 +- Don't hide floating panels when we maximize some other panel: http://trac.wxwidgets.org/ticket/4066 +- wxAUINotebook incorrect ALLOW_ACTIVE_PANE handling: http://trac.wxwidgets.org/ticket/4361 +- Page changing veto doesn't work, (patch supplied): http://trac.wxwidgets.org/ticket/4518 +- Show and DoShow are mixed around in wxAuiMDIChildFrame: http://trac.wxwidgets.org/ticket/4567 +- wxAuiManager & wxToolBar - ToolBar Of Size Zero: http://trac.wxwidgets.org/ticket/9724 +- wxAuiNotebook doesn't behave properly like a container as far as...: http://trac.wxwidgets.org/ticket/9911 +- Serious layout bugs in wxAUI: http://trac.wxwidgets.org/ticket/10620 +- wAuiDefaultTabArt::Clone() should just use copy contructor: http://trac.wxwidgets.org/ticket/11388 +- Drop down button for check tool on wxAuiToolbar: http://trac.wxwidgets.org/ticket/11139 + +Plus the following features: + +- AuiManager: + + (a) Implementation of a simple minimize pane system: Clicking on this minimize button causes a new + AuiToolBar to be created and added to the frame manager, (currently the implementation is such + that panes at West will have a toolbar at the right, panes at South will have toolbars at the + bottom etc...) and the pane is hidden in the manager. + Clicking on the restore button on the newly created toolbar will result in the toolbar being + removed and the original pane being restored; + (b) Panes can be docked on top of each other to form `AuiNotebooks`; `AuiNotebooks` tabs can be torn + off to create floating panes; + (c) On Windows XP, use the nice sash drawing provided by XP while dragging the sash; + (d) Possibility to set an icon on docked panes; + (e) Possibility to draw a sash visual grip, for enhanced visualization of sashes; + (f) Implementation of a native docking art (`ModernDockArt`). Windows XP only, **requires** Mark Hammond's + pywin32 package (winxptheme); + (g) Possibility to set a transparency for floating panes (a la Paint .NET); + (h) Snapping the main frame to the screen in any positin specified by horizontal and vertical + alignments; + (i) Snapping floating panes on left/right/top/bottom or any combination of directions, a la Winamp; + (j) "Fly-out" floating panes, i.e. panes which show themselves only when the mouse hover them; + (k) Ability to set custom bitmaps for pane buttons (close, maximize, etc...); + (l) Implementation of the style ``AUI_MGR_ANIMATE_FRAMES``, which fade-out floating panes when + they are closed (all platforms which support frames transparency) and show a moving rectangle + when they are docked and minimized (Windows < Vista and GTK only); + (m) A pane switcher dialog is available to cycle through existing AUI panes; + (n) Some flags which allow to choose the orientation and the position of the minimized panes; + (o) The functions [Get]MinimizeMode() in `AuiPaneInfo` which allow to set/get the flags described above; + (p) Events like ``EVT_AUI_PANE_DOCKING``, ``EVT_AUI_PANE_DOCKED``, ``EVT_AUI_PANE_FLOATING`` and ``EVT_AUI_PANE_FLOATED`` are + available for all panes *except* toolbar panes; + (q) Implementation of the RequestUserAttention method for panes; + (r) Ability to show the caption bar of docked panes on the left instead of on the top (with caption + text rotated by 90 degrees then). This is similar to what `wxDockIt` did. To enable this feature on any + given pane, simply call `CaptionVisible(True, left=True)`; + (s) New Aero-style docking guides: you can enable them by using the `AuiManager` style ``AUI_MGR_AERO_DOCKING_GUIDES``; + (t) A slide-in/slide-out preview of minimized panes can be seen by enabling the `AuiManager` style + ``AUI_MGR_PREVIEW_MINIMIZED_PANES`` and by hovering with the mouse on the minimized pane toolbar tool; + (u) New Whidbey-style docking guides: you can enable them by using the `AuiManager` style ``AUI_MGR_WHIDBEY_DOCKING_GUIDES``; + (v) Native of custom-drawn mini frames can be used as floating panes, depending on the ``AUI_MGR_USE_NATIVE_MINIFRAMES`` style; + (w) A "smooth docking effect" can be obtained by using the ``AUI_MGR_SMOOTH_DOCKING`` style (similar to PyQT docking style). + +| + +- AuiNotebook: + + (a) Implementation of the style ``AUI_NB_HIDE_ON_SINGLE_TAB``, a la `wx.lib.agw.flatnotebook`; + (b) Implementation of the style ``AUI_NB_SMART_TABS``, a la `wx.lib.agw.flatnotebook`; + (c) Implementation of the style ``AUI_NB_USE_IMAGES_DROPDOWN``, which allows to show tab images + on the tab dropdown menu instead of bare check menu items (a la `wx.lib.agw.flatnotebook`); + (d) 6 different tab arts are available, namely: + + (1) Default "glossy" theme (as in `wx.aui.AuiNotebook`) + (2) Simple theme (as in `wx.aui.AuiNotebook`) + (3) Firefox 2 theme + (4) Visual Studio 2003 theme (VC71) + (5) Visual Studio 2005 theme (VC81) + (6) Google Chrome theme + + (e) Enabling/disabling tabs; + (f) Setting the colour of the tab's text; + (g) Implementation of the style ``AUI_NB_CLOSE_ON_TAB_LEFT``, which draws the tab close button on + the left instead of on the right (a la Camino browser); + (h) Ability to save and load perspectives in `AuiNotebook` (experimental); + (i) Possibility to add custom buttons in the `AuiNotebook` tab area; + (j) Implementation of the style ``AUI_NB_TAB_FLOAT``, which allows the floating of single tabs. + Known limitation: when the notebook is more or less full screen, tabs cannot be dragged far + enough outside of the notebook to become floating pages; + (k) Implementation of the style ``AUI_NB_DRAW_DND_TAB`` (on by default), which draws an image + representation of a tab while dragging; + (l) Implementation of the `AuiNotebook` unsplit functionality, which unsplit a splitted AuiNotebook + when double-clicking on a sash; + (m) Possibility to hide all the tabs by calling `HideAllTAbs`; + (n) wxPython controls can now be added inside page tabs by calling `AddControlToPage`, and they can be + removed by calling `RemoveControlFromPage`; + (o) Possibility to preview all the pages in a `AuiNotebook` (as thumbnails) by using the `NotebookPreview` + method of `AuiNotebook`; + (p) Tab labels can be edited by calling the `SetRenamable` method on a `AuiNotebook` page; + (q) Support for multi-lines tab labels in `AuiNotebook`; + (r) Support for setting minimum and maximum tab widths for fixed width tabs; + (s) Implementation of the style ``AUI_NB_ORDER_BY_ACCESS``, which orders the tabs by last access time + inside the Tab Navigator dialog; + (t) Implementation of the style ``AUI_NB_NO_TAB_FOCUS``, allowing the developer not to draw the tab + focus rectangle on tne `AuiNotebook` tabs. + +| + +- AuiToolBar: + + (a) ``AUI_TB_PLAIN_BACKGROUND`` style that allows to easy setup a plain background to the AUI toolbar, + without the need to override drawing methods. This style contrasts with the default behaviour + of the `wx.aui.AuiToolBar` that draws a background gradient and this break the window design when + putting it within a control that has margin between the borders and the toolbar (example: put + `wx.aui.AuiToolBar` within a `wx.StaticBoxSizer` that has a plain background); + (b) `AuiToolBar` allow item alignment: http://trac.wxwidgets.org/ticket/10174; + (c) `AUIToolBar` `DrawButton()` improvement: http://trac.wxwidgets.org/ticket/10303; + (d) `AuiToolBar` automatically assign new id for tools: http://trac.wxwidgets.org/ticket/10173; + (e) `AuiToolBar` Allow right-click on any kind of button: http://trac.wxwidgets.org/ticket/10079; + (f) `AuiToolBar` idle update only when visible: http://trac.wxwidgets.org/ticket/10075; + (g) Ability of creating `AuiToolBar` tools with [counter]clockwise rotation. This allows to propose a + variant of the minimizing functionality with a rotated button which keeps the caption of the pane + as label; + (h) Allow setting the alignment of all tools in a toolbar that is expanded. + + +TODOs +===== + +- Documentation, documentation and documentation; +- Fix `tabmdi.AuiMDIParentFrame` and friends, they do not work correctly at present; +- Allow specification of `CaptionLeft()` to `AuiPaneInfo` to show the caption bar of docked panes + on the left instead of on the top (with caption text rotated by 90 degrees then). This is + similar to what `wxDockIt` did - DONE; +- Make developer-created `AuiNotebooks` and automatic (framemanager-created) `AuiNotebooks` behave + the same way (undocking of tabs) - DONE, to some extent; +- Find a way to dock panes in already floating panes (`AuiFloatingFrames`), as they already have + their own `AuiManager`; +- Add more gripper styles (see, i.e., PlusDock 4.0); +- Add an "AutoHide" feature to docked panes, similar to fly-out floating panes (see, i.e., PlusDock 4.0); +- Add events for panes when they are about to float or to be docked (something like + ``EVT_AUI_PANE_FLOATING/ED`` and ``EVT_AUI_PANE_DOCKING/ED``) - DONE, to some extent; +- Implement the 4-ways splitter behaviour for horizontal and vertical sashes if they intersect; +- Extend `tabart.py` with more aui tab arts; +- Implement ``AUI_NB_LEFT`` and ``AUI_NB_RIGHT`` tab locations in `AuiNotebook`; +- Move `AuiDefaultToolBarArt` into a separate module (as with `tabart.py` and `dockart.py`) and + provide more arts for toolbars (maybe from `wx.lib.agw.flatmenu`?) +- Support multiple-rows/multiple columns toolbars; +- Integrate as much as possible with `wx.lib.agw.flatmenu`, from dropdown menus in `AuiNotebook` to + toolbars and menu positioning; +- Possibly handle minimization of panes in a different way (or provide an option to switch to + another way of minimizing panes); +- Clean up/speed up the code, especially time-consuming for-loops; +- Possibly integrate `wxPyRibbon` (still on development), at least on Windows. + + +License And Version +=================== + +AUI library is distributed under the wxPython license. + +Latest revision: Andrea Gavana @ 10 Mar 2011, 15.00 GMT + +Version 1.3. + +""" + +__author__ = "Andrea Gavana " +__date__ = "31 March 2009" + + +from aui_constants import * +from aui_utilities import * +from auibar import * +from auibook import * +from tabart import * +from dockart import * +from framemanager import * +from tabmdi import * diff --git a/agw/aui/__init__.pyc b/agw/aui/__init__.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e40c0ef09c5e76e3eeead72941efbc07b2a01d9e GIT binary patch literal 15993 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 M^Uqd3S^VGs067YsJpcdz literal 0 HcmV?d00001 diff --git a/agw/aui/aui_constants.py b/agw/aui/aui_constants.py new file mode 100644 index 0000000..38f183d --- /dev/null +++ b/agw/aui/aui_constants.py @@ -0,0 +1,2588 @@ +""" +This module contains all the constants used by wxPython-AUI. + +Especially important and meaningful are constants for AuiManager, AuiDockArt and +AuiNotebook. +""" + +__author__ = "Andrea Gavana " +__date__ = "31 March 2009" + + +import wx +from wx.lib.embeddedimage import PyEmbeddedImage + +# ------------------------- # +# - AuiNotebook Constants - # +# ------------------------- # + +# For tabart +# -------------- + +vertical_border_padding = 4 +""" Border padding used in drawing tabs. """ + +if wx.Platform == "__WXMAC__": + nb_close_bits = "\xFF\xFF\xFF\xFF\x0F\xFE\x03\xF8\x01\xF0\x19\xF3" \ + "\xB8\xE3\xF0\xE1\xE0\xE0\xF0\xE1\xB8\xE3\x19\xF3" \ + "\x01\xF0\x03\xF8\x0F\xFE\xFF\xFF" + """ AuiNotebook close button image on wxMAC. """ + +elif wx.Platform == "__WXGTK__": + nb_close_bits = "\xff\xff\xff\xff\x07\xf0\xfb\xef\xdb\xed\x8b\xe8" \ + "\x1b\xec\x3b\xee\x1b\xec\x8b\xe8\xdb\xed\xfb\xef" \ + "\x07\xf0\xff\xff\xff\xff\xff\xff" + """ AuiNotebook close button image on wxGTK. """ + +else: + nb_close_bits = "\xff\xff\xff\xff\xff\xff\xff\xff\xe7\xf3\xcf\xf9" \ + "\x9f\xfc\x3f\xfe\x3f\xfe\x9f\xfc\xcf\xf9\xe7\xf3" \ + "\xff\xff\xff\xff\xff\xff\xff\xff" + """ AuiNotebook close button image on wxMSW. """ + +nb_left_bits = "\xff\xff\xff\xff\xff\xff\xff\xfe\x7f\xfe\x3f\xfe\x1f" \ + "\xfe\x0f\xfe\x1f\xfe\x3f\xfe\x7f\xfe\xff\xfe\xff\xff" \ + "\xff\xff\xff\xff\xff\xff" +""" AuiNotebook left button image. """ + +nb_right_bits = "\xff\xff\xff\xff\xff\xff\xdf\xff\x9f\xff\x1f\xff\x1f" \ + "\xfe\x1f\xfc\x1f\xfe\x1f\xff\x9f\xff\xdf\xff\xff\xff" \ + "\xff\xff\xff\xff\xff\xff" +""" AuiNotebook right button image. """ + +nb_list_bits = "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x0f" \ + "\xf8\xff\xff\x0f\xf8\x1f\xfc\x3f\xfe\x7f\xff\xff\xff" \ + "\xff\xff\xff\xff\xff\xff" +""" AuiNotebook windows list button image. """ + + +#---------------------------------------------------------------------- +tab_active_center = PyEmbeddedImage( + "iVBORw0KGgoAAAANSUhEUgAAAAEAAAAbCAYAAAC9WOV0AAAABHNCSVQICAgIfAhkiAAAADNJ" + "REFUCJltzMEJwDAUw9DHX6OLdP/Bop4KDc3F2EIYrsFtrZow8GnH6OD1zvRTajvY2QMHIhNx" + "jUhuAgAAAABJRU5ErkJggg==") +""" Center active tab image for the Chrome tab art. """ + +#---------------------------------------------------------------------- +tab_active_left = PyEmbeddedImage( + "iVBORw0KGgoAAAANSUhEUgAAAA8AAAAbCAYAAACjkdXHAAAABHNCSVQICAgIfAhkiAAAAglJ" + "REFUOI2Nkk9rE0EYh5/J7mpW06xE2iSmeFHxEoqIAc/FQ5CKgn4DP4KlIQG/QVsQbBEKgop+" + "Anvy4rV4bLT2JCGJPVXqwaZJd+f1kN26WTfJDrzszDLPPL/5o0jeFGAC54A0YKmEYAo4DzjA" + "LHAZmElqtIGrhmEsvtzcfPNtb6/V6524SWALKBiGsfhxe/uzFhGth5XEmgVubWxsvA1Az68k" + "1nngYbPZ7ASg69c06wxwe3V9/b3reVqHwGmwCZRs2370fX//wIuA0+CLwEKj0XilZTSu602G" + "FcP7vLe7+7XlRaCgPw62gGv5fP6p63raiwFdLWKOgdNArl6vV1UqpQgcYdcYbwooAPfb7c7h" + "mTWmUjGwCWTL5fL1K6VSLiqQyMTYyLVa/UEwe9IC0chFYKnb/XnkeiIDV+Q0UsG/qNkCnEql" + "crNQLDpaxpskJnYayD1bXl4S/xrDoPLHKjQOmsHwlCuHv44+ZJ2sLTrGGqzg7zEc+VK1Wl1w" + "HMcG0DFxw6sFsRVwAZhdWak9FoRJ+w2HCKzzwN3jXv+daVmGDkdWoMKb9fumHz0DFFfX1p5Y" + "lmXo6N0G48jzVEDOt97pdA9ezOXzGU+PzBmN6VuDqyoDN3Z2vjyfKxQynhYkJuJ/L02Ara3X" + "n3602r8HrpaTUy3HAy1/+hNq8O+r+q4WETirmFMNBwm3v+gdmytKNIUpAAAAAElFTkSuQmCC") +""" Left active tab image for the Chrome tab art. """ + +#---------------------------------------------------------------------- +tab_active_right = PyEmbeddedImage( + "iVBORw0KGgoAAAANSUhEUgAAAA8AAAAbCAYAAACjkdXHAAAABHNCSVQICAgIfAhkiAAAAkpJ" + "REFUOI2NlM1rU0EUxX9zZ5KaWq3GKKnGutC0FEWCWAWLRUOxBetK/wdp6Re6F6TFXXGhuFdw" + "b7dCQUUpiFt1XbB2q7Uf1iTvunjzkpe0afNgmLnDnHvOPe/OWCALtAFC+Cktfha4CRwBDnhg" + "BQhaSrK19bf89dv35WfPX7y01haBbiAFmH3BlUA1Gm8WFt75BFkg0TK4VAl0Y3NL5+efvgIK" + "wOH92EVjxRljGBi4VgTOeLDbk7kcqEZju1TWX7/Xgtm5J6+BS8ChvdilLhAhkUya4eFbxVQq" + "1e3ZbUtgg8GKJd/Tk70/NjYCHCPsgX1kV8K5VA70z8amfvy0tAwMAcebSRfijikY8ez5/OlM" + "JrOncbIjp4K1lmRb0sw8eDgCpAm7rwlz46YIzjpGb48WveyDNPhDfCOuHmNwzpHL5dK9fX3n" + "mkmvaxJiayOCWMvM1PSdZtJrhiloLJMYIeESDFwf7Acyu0mXGLYmX0PpYi3ZbFdnoVDoBTpp" + "uCxCjFob1tYKzlnGJyZHd5Mu6uVGkqvMCmCwzjE4eOMqcALoINauUic37hjhLXPWcTSdThWL" + "QxcJX5yqdGk4H/cP9a4755iYnLpL+M/b8e0qjafrekb9TUskuNx/5TzQ5Y1zO9yOZEd1R7OI" + "JdXebh/Pzt3zCToAMZv/AjU1orDWWKAGVJVSqcTqysp6X+/ZaeAL8KNac9wsVQ8yNeOsdZw8" + "let4/2HpEdAPXDAb20HLj7xqeHT158ra4uLbz2bdg03krmetxrH9KDAmHP8Bn0j1t/01UV0A" + "AAAASUVORK5CYII=") +""" Right active tab image for the Chrome tab art. """ + +#---------------------------------------------------------------------- +tab_close = PyEmbeddedImage( + "iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAYAAABWdVznAAAABHNCSVQICAgIfAhkiAAAAI9J" + "REFUKJG90MEKAWEUxfEfM4rxAFIommzZzNb7v4BsLJTsiGQlYjHfME3flrO75/xvnXv5p/qY" + "R/wcWTUktWCKFbrYB6/AAhecmwunAI/RwQAjbLGpoFakwjLATxzqMLQjC68A3/FohkljLkKN" + "Ha4YKg8+VkBag3Pll9a1GikmuPk+4qMMs0jFMXoR/0d6A9JRFV/jxY+iAAAAAElFTkSuQmCC") +""" Normal close button image for the Chrome tab art. """ + +#---------------------------------------------------------------------- +tab_close_h = PyEmbeddedImage( + "iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAYAAABWdVznAAAABHNCSVQICAgIfAhkiAAAAOlJ" + "REFUKJGVkiFuw0AQRd849hUS7iPUwGEllhyjYJ+gaK9Q4CsY9QTFIY4shQQucI8Q7l6h3Z0S" + "r7UgjdrPZvVm52k0wpJLWe4y51qgVpECQFQnYPzabN4ra2cAAbgWxZMmyavAkTtROIn33fM0" + "fcilLHep92+/wXHTd5K8JJlzbYD3w8C2aVZo2zTsh4FF5Zg516ZAHYBb35MbszbkxnDr+3hQ" + "napIIUv1eT6vYPggvAGoSJE88r6XVFQnRA7BOdYIk8IUUZ1SYAQOsXOskRsT1+P/11pZO4v3" + "ncLpESzed5W1c1jQn0/jBzPfck1qdmfjAAAAAElFTkSuQmCC") +""" Hover close button image for the Chrome tab art. """ + +#---------------------------------------------------------------------- +tab_close_p = PyEmbeddedImage( + "iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAYAAABWdVznAAAABHNCSVQICAgIfAhkiAAAASxJ" + "REFUKJF9kbFLQlEYxX/nvbs55OAkiJAE7k7Nibo9xf+hrTlyr3Boipb+BCGq0bApJEQcG0Ms" + "aQ0Lmq5+Dc+nDtbZ7uHce37fd8VSlWwh50PfRKqClWJXI8y6bu5uHj5e3wEEcJDP75txLBSx" + "RYbdS7QfJ5PnsJIt5BbB4hQjkrQtjxlFILOXyvQDH/qmUCSJznDAYetkFTxsndAZDggkhCIf" + "+qaLmWP1bu8oN+qrC+VGnd7t3bpKqrp4wBjl+ux8FUweSLwlXCnYCv2PHGgE1BLmTYykad2i" + "kcOsi1TbZN7EKDfq67NZV5VsIeedvzQjCv5YK8R/4bw7Cl+/P7920+kJkBEq/hWWaPem45cQ" + "YDybTfdSmf5CizckwHaAH9ATZldu7i560/ELwC+6RXdU6KzezAAAAABJRU5ErkJggg==") +""" Pressed close button image for the Chrome tab art. """ + +#---------------------------------------------------------------------- +tab_inactive_center = PyEmbeddedImage( + "iVBORw0KGgoAAAANSUhEUgAAAAEAAAAbCAYAAAC9WOV0AAAABHNCSVQICAgIfAhkiAAAAElJ" + "REFUCJlVyiEOgDAUBNHp3qmX5iYkyMpqBAaFILRdDGn4qybZB98yy3ZZrRu1PpABAQiDSLN+" + "h4NLEU8CBAfoPHZUywr3M/wCTz8c3/qQrUcAAAAASUVORK5CYII=") +""" Center inactive tab image for the Chrome tab art. """ + +#---------------------------------------------------------------------- +tab_inactive_left = PyEmbeddedImage( + "iVBORw0KGgoAAAANSUhEUgAAAA8AAAAbCAYAAACjkdXHAAAABHNCSVQICAgIfAhkiAAAAf5J" + "REFUOI2llE1rE1EUhp8bZwyhaZomk5DaD40hSWPQVkTd6KIIEUWlLqTEhTaLulBQ6sfKjeBC" + "ECULXQku/Alx7d6/U1EQae45LjJpJ5NOnOKBgYG5z33Px3sG/iPMIc87QAmYBZKHgdOu69a2" + "3/W2yrVGK5vPLTlxFV3Xrb3+8v1Ntd5oiSpWBmnEidKT972tar3R6ovSt4qoxoIdoFipNlpW" + "B6AVRYFEHNWn3a8dz/PK1rIHEgN2UpnMseVTK7fUGBME48CFe88+3sh5+SXr1xmMSbABvJXz" + "l9siYAVGWJ0Mu/OVZr5Q8CpWfFWzD2Imj2qu/fhtG4wRVUIZg0bDBsgtn15dt6qIKKBDQZ81" + "kWmnzly6OZ+ZzhSt7jfK6CBjFMwEk5TWOy82AVQGhzVUb5RJEkC2fLK6JgIiPhioeZJJUhev" + "3j2RTqdzooqge2ojCxwxqrnrG4/uq4Ida3HgAjMOJ4CZSq1+RVBUzCgQinDDstfa282jyeTU" + "rhUGF4CJgMPKhbXbmw9VFfG7fBA4LCao7AAzi8cXz1kF0dENMqH38KgWnnd7nSMJxxE5wI4+" + "MHyCaeeAYvPshQ0RJby3wVSDHxxgAVh99elb9/evndmfP3boW2FsqGNhMMCdBy8/fJ5KZ6at" + "qL+3Q1dEzFkNGMX82ZWh18e0/vVT/wuFmdYVv/ruKgAAAABJRU5ErkJggg==") +""" Left inactive tab image for the Chrome tab art. """ + +#---------------------------------------------------------------------- +tab_inactive_right = PyEmbeddedImage( + "iVBORw0KGgoAAAANSUhEUgAAAA8AAAAbCAYAAACjkdXHAAAABHNCSVQICAgIfAhkiAAAAhBJ" + "REFUOI2llM9rE1EQxz8zb1dSTKNuYtW01kQDRoKFWi9FEEq1IooUUWoPokWCtVqkR69KsSBU" + "8OJRPOhBxZNe/At6FBER/HFUPEq1IGn3ecgm2ZjdJODCHPY9vvP9fufNDPzHZ4DDQBrYBKwB" + "ftfoJys/Kw9ef/1y8/6rh67rHgKS3WLl6cqqtcCGD58+vn+zdPXorUql8g5Y7wTWdd+y4Vus" + "teQK+yfKi8/KwM5umBXAAgioCIP54gTQBzgdwTbsQZR0JpOfXXw+0w27hn9EBGMcyRcPnulJ" + "pbKd2JvACKgKnpcePH99+TSwvT3YEphusKsqB4ZHp4FMNWUn5loSEVSFbZ63b8eeUhpwu5Md" + "JBFRjHHk7LXb08CuNuAaZTgEEaFQHJoEvDjpakOYmnURUFWSvam+0ujJfqAnmlnABhG2jlTZ" + "j19YuEzMm7dUu34hihrDQG7vGLCViPq0VruuvdquyWSvN3xsKhclvbXaoUQiihFlfLJ8iYiq" + "O/EtUC2xGGF3vjAObAnI6stCsZbYCLwnEonNY+dulALvHWSH2YN2PXLq4hz/9HpjnmOs18DZ" + "bP9IIL0+afV5juqzRgLFcV1n9u6LGWAgWnaMBFHBOIbi0MgU1S3jAcjyyw9xqpvzWou1Pj++" + "f/t8b/7EAvBW5u48agU37abWs99rv1YfL81fkT8V34YxbZ696d4CfwEszZSZx6Z26wAAAABJ" + "RU5ErkJggg==") +""" Right inactive tab image for the Chrome tab art. """ + +# For auibook +# ----------- + +AuiBaseTabCtrlId = 5380 +""" Base window identifier for AuiTabCtrl. """ + +AUI_NB_TOP = 1 << 0 +""" With this style, tabs are drawn along the top of the notebook. """ +AUI_NB_LEFT = 1 << 1 # not implemented yet +""" With this style, tabs are drawn along the left of the notebook. +Not implemented yet. """ +AUI_NB_RIGHT = 1 << 2 # not implemented yet +""" With this style, tabs are drawn along the right of the notebook. +Not implemented yet. """ +AUI_NB_BOTTOM = 1 << 3 +""" With this style, tabs are drawn along the bottom of the notebook. """ +AUI_NB_TAB_SPLIT = 1 << 4 +""" Allows the tab control to be split by dragging a tab. """ +AUI_NB_TAB_MOVE = 1 << 5 +""" Allows a tab to be moved horizontally by dragging. """ +AUI_NB_TAB_EXTERNAL_MOVE = 1 << 6 +""" Allows a tab to be moved to another tab control. """ +AUI_NB_TAB_FIXED_WIDTH = 1 << 7 +""" With this style, all tabs have the same width. """ +AUI_NB_SCROLL_BUTTONS = 1 << 8 +""" With this style, left and right scroll buttons are displayed. """ +AUI_NB_WINDOWLIST_BUTTON = 1 << 9 +""" With this style, a drop-down list of windows is available. """ +AUI_NB_CLOSE_BUTTON = 1 << 10 +""" With this style, a close button is available on the tab bar. """ +AUI_NB_CLOSE_ON_ACTIVE_TAB = 1 << 11 +""" With this style, a close button is available on the active tab. """ +AUI_NB_CLOSE_ON_ALL_TABS = 1 << 12 +""" With this style, a close button is available on all tabs. """ +AUI_NB_MIDDLE_CLICK_CLOSE = 1 << 13 +""" Allows to close `AuiNotebook` tabs by mouse middle button click. """ +AUI_NB_SUB_NOTEBOOK = 1 << 14 +""" This style is used by `AuiManager` to create automatic `AuiNotebooks`. """ +AUI_NB_HIDE_ON_SINGLE_TAB = 1 << 15 +""" Hides the tab window if only one tab is present. """ +AUI_NB_SMART_TABS = 1 << 16 +""" Use `Smart Tabbing`, like ``Alt`` + ``Tab`` on Windows. """ +AUI_NB_USE_IMAGES_DROPDOWN = 1 << 17 +""" Uses images on dropdown window list menu instead of check items. """ +AUI_NB_CLOSE_ON_TAB_LEFT = 1 << 18 +""" Draws the tab close button on the left instead of on the right +(a la Camino browser). """ +AUI_NB_TAB_FLOAT = 1 << 19 +""" Allows the floating of single tabs. +Known limitation: when the notebook is more or less full screen, tabs +cannot be dragged far enough outside of the notebook to become +floating pages. """ +AUI_NB_DRAW_DND_TAB = 1 << 20 +""" Draws an image representation of a tab while dragging. """ +AUI_NB_ORDER_BY_ACCESS = 1 << 21 +""" Tab navigation order by last access time. """ +AUI_NB_NO_TAB_FOCUS = 1 << 22 +""" Don't draw tab focus rectangle. """ + +AUI_NB_DEFAULT_STYLE = AUI_NB_TOP | AUI_NB_TAB_SPLIT | AUI_NB_TAB_MOVE | \ + AUI_NB_SCROLL_BUTTONS | AUI_NB_CLOSE_ON_ACTIVE_TAB | \ + AUI_NB_MIDDLE_CLICK_CLOSE | AUI_NB_DRAW_DND_TAB +""" Default `AuiNotebook` style. """ + +#---------------------------------------------------------------------- +Mondrian = PyEmbeddedImage( + "iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAABHNCSVQICAgIfAhkiAAAAHFJ" + "REFUWIXt1jsKgDAQRdF7xY25cpcWC60kioI6Fm/ahHBCMh+BRmGMnAgEWnvPpzK8dvrFCCCA" + "coD8og4c5Lr6WB3Q3l1TBwLYPuF3YS1gn1HphgEEEABcKERrGy0E3B0HFJg7C1N/f/kTBBBA" + "+Vi+AMkgFEvBPD17AAAAAElFTkSuQmCC") +""" Default icon for the Smart Tabbing dialog. """ + +# -------------------------- # +# - FrameManager Constants - # +# -------------------------- # + +# Docking Styles +AUI_DOCK_NONE = 0 +""" No docking direction. """ +AUI_DOCK_TOP = 1 +""" Top docking direction. """ +AUI_DOCK_RIGHT = 2 +""" Right docking direction. """ +AUI_DOCK_BOTTOM = 3 +""" Bottom docking direction. """ +AUI_DOCK_LEFT = 4 +""" Left docking direction. """ +AUI_DOCK_CENTER = 5 +""" Center docking direction. """ +AUI_DOCK_CENTRE = AUI_DOCK_CENTER +""" Centre docking direction. """ +AUI_DOCK_NOTEBOOK_PAGE = 6 +""" Automatic AuiNotebooks docking style. """ + +# Floating/Dragging Styles +AUI_MGR_ALLOW_FLOATING = 1 << 0 +""" Allow floating of panes. """ +AUI_MGR_ALLOW_ACTIVE_PANE = 1 << 1 +""" If a pane becomes active, "highlight" it in the interface. """ +AUI_MGR_TRANSPARENT_DRAG = 1 << 2 +""" If the platform supports it, set transparency on a floating pane +while it is dragged by the user. """ +AUI_MGR_TRANSPARENT_HINT = 1 << 3 +""" If the platform supports it, show a transparent hint window when +the user is about to dock a floating pane. """ +AUI_MGR_VENETIAN_BLINDS_HINT = 1 << 4 +""" Show a "venetian blind" effect when the user is about to dock a +floating pane. """ +AUI_MGR_RECTANGLE_HINT = 1 << 5 +""" Show a rectangle hint effect when the user is about to dock a +floating pane. """ +AUI_MGR_HINT_FADE = 1 << 6 +""" If the platform supports it, the hint window will fade in and out. """ +AUI_MGR_NO_VENETIAN_BLINDS_FADE = 1 << 7 +""" Disables the "venetian blind" fade in and out. """ +AUI_MGR_LIVE_RESIZE = 1 << 8 +""" Live resize when the user drag a sash. """ +AUI_MGR_ANIMATE_FRAMES = 1 << 9 +""" Fade-out floating panes when they are closed (all platforms which support +frames transparency) and show a moving rectangle when they are docked +(Windows < Vista and GTK only). """ +AUI_MGR_AERO_DOCKING_GUIDES = 1 << 10 +""" Use the new Aero-style bitmaps as docking guides. """ +AUI_MGR_PREVIEW_MINIMIZED_PANES = 1 << 11 +""" Slide in and out minimized panes to preview them. """ +AUI_MGR_WHIDBEY_DOCKING_GUIDES = 1 << 12 +""" Use the new Whidbey-style bitmaps as docking guides. """ +AUI_MGR_SMOOTH_DOCKING = 1 << 13 +""" Performs a "smooth" docking of panes (a la PyQT). """ +AUI_MGR_USE_NATIVE_MINIFRAMES = 1 << 14 +""" Use miniframes with native caption bar as floating panes instead or custom +drawn caption bars (forced on wxMac). """ +AUI_MGR_AUTONB_NO_CAPTION = 1 << 15 +""" Panes that merge into an automatic notebook will not have the pane +caption visible. """ + + +AUI_MGR_DEFAULT = AUI_MGR_ALLOW_FLOATING | AUI_MGR_TRANSPARENT_HINT | \ + AUI_MGR_HINT_FADE | AUI_MGR_NO_VENETIAN_BLINDS_FADE +""" Default `AuiManager` style. """ + +# Panes Customization +AUI_DOCKART_SASH_SIZE = 0 +""" Customizes the sash size. """ +AUI_DOCKART_CAPTION_SIZE = 1 +""" Customizes the caption size. """ +AUI_DOCKART_GRIPPER_SIZE = 2 +""" Customizes the gripper size. """ +AUI_DOCKART_PANE_BORDER_SIZE = 3 +""" Customizes the pane border size. """ +AUI_DOCKART_PANE_BUTTON_SIZE = 4 +""" Customizes the pane button size. """ +AUI_DOCKART_BACKGROUND_COLOUR = 5 +""" Customizes the background colour. """ +AUI_DOCKART_BACKGROUND_GRADIENT_COLOUR = 6 +""" Customizes the background gradient colour. """ +AUI_DOCKART_SASH_COLOUR = 7 +""" Customizes the sash colour. """ +AUI_DOCKART_ACTIVE_CAPTION_COLOUR = 8 +""" Customizes the active caption colour. """ +AUI_DOCKART_ACTIVE_CAPTION_GRADIENT_COLOUR = 9 +""" Customizes the active caption gradient colour. """ +AUI_DOCKART_INACTIVE_CAPTION_COLOUR = 10 +""" Customizes the inactive caption colour. """ +AUI_DOCKART_INACTIVE_CAPTION_GRADIENT_COLOUR = 11 +""" Customizes the inactive gradient caption colour. """ +AUI_DOCKART_ACTIVE_CAPTION_TEXT_COLOUR = 12 +""" Customizes the active caption text colour. """ +AUI_DOCKART_INACTIVE_CAPTION_TEXT_COLOUR = 13 +""" Customizes the inactive caption text colour. """ +AUI_DOCKART_BORDER_COLOUR = 14 +""" Customizes the border colour. """ +AUI_DOCKART_GRIPPER_COLOUR = 15 +""" Customizes the gripper colour. """ +AUI_DOCKART_CAPTION_FONT = 16 +""" Customizes the caption font. """ +AUI_DOCKART_GRADIENT_TYPE = 17 +""" Customizes the gradient type (no gradient, vertical or horizontal). """ +AUI_DOCKART_DRAW_SASH_GRIP = 18 +""" Draw a sash grip on the sash. """ + +# Caption Gradient Type +AUI_GRADIENT_NONE = 0 +""" No gradient on the captions. """ +AUI_GRADIENT_VERTICAL = 1 +""" Vertical gradient on the captions. """ +AUI_GRADIENT_HORIZONTAL = 2 +""" Horizontal gradient on the captions. """ + +# Pane Button State +AUI_BUTTON_STATE_NORMAL = 0 +""" Normal button state. """ +AUI_BUTTON_STATE_HOVER = 1 << 1 +""" Hovered button state. """ +AUI_BUTTON_STATE_PRESSED = 1 << 2 +""" Pressed button state. """ +AUI_BUTTON_STATE_DISABLED = 1 << 3 +""" Disabled button state. """ +AUI_BUTTON_STATE_HIDDEN = 1 << 4 +""" Hidden button state. """ +AUI_BUTTON_STATE_CHECKED = 1 << 5 +""" Checked button state. """ + +# Pane minimize mode +AUI_MINIMIZE_POS_SMART = 0x01 +""" Minimizes the pane on the closest tool bar. """ +AUI_MINIMIZE_POS_TOP = 0x02 +""" Minimizes the pane on the top tool bar. """ +AUI_MINIMIZE_POS_LEFT = 0x03 +""" Minimizes the pane on its left tool bar. """ +AUI_MINIMIZE_POS_RIGHT = 0x04 +""" Minimizes the pane on its right tool bar. """ +AUI_MINIMIZE_POS_BOTTOM = 0x05 +""" Minimizes the pane on its bottom tool bar. """ +AUI_MINIMIZE_POS_MASK = 0x07 +""" Mask to filter the position flags. """ +AUI_MINIMIZE_CAPT_HIDE = 0 +""" Hides the caption of the minimized pane. """ +AUI_MINIMIZE_CAPT_SMART = 0x08 +""" Displays the caption in the best rotation (horz or clockwise). """ +AUI_MINIMIZE_CAPT_HORZ = 0x10 +""" Displays the caption horizontally. """ +AUI_MINIMIZE_CAPT_MASK = 0x18 +""" Mask to filter the caption flags. """ + +# Button kind +AUI_BUTTON_CLOSE = 101 +""" Shows a close button on the pane. """ +AUI_BUTTON_MAXIMIZE_RESTORE = 102 +""" Shows a maximize/restore button on the pane. """ +AUI_BUTTON_MINIMIZE = 103 +""" Shows a minimize button on the pane. """ +AUI_BUTTON_PIN = 104 +""" Shows a pin button on the pane. """ +AUI_BUTTON_OPTIONS = 105 +""" Shows an option button on the pane (not implemented). """ +AUI_BUTTON_WINDOWLIST = 106 +""" Shows a window list button on the pane (for AuiNotebook). """ +AUI_BUTTON_LEFT = 107 +""" Shows a left button on the pane (for AuiNotebook). """ +AUI_BUTTON_RIGHT = 108 +""" Shows a right button on the pane (for AuiNotebook). """ +AUI_BUTTON_UP = 109 +""" Shows an up button on the pane (not implemented). """ +AUI_BUTTON_DOWN = 110 +""" Shows a down button on the pane (not implemented). """ +AUI_BUTTON_CUSTOM1 = 201 +""" Shows a custom button on the pane. """ +AUI_BUTTON_CUSTOM2 = 202 +""" Shows a custom button on the pane. """ +AUI_BUTTON_CUSTOM3 = 203 +""" Shows a custom button on the pane. """ +AUI_BUTTON_CUSTOM4 = 204 +""" Shows a custom button on the pane. """ +AUI_BUTTON_CUSTOM5 = 205 +""" Shows a custom button on the pane. """ +AUI_BUTTON_CUSTOM6 = 206 +""" Shows a custom button on the pane. """ +AUI_BUTTON_CUSTOM7 = 207 +""" Shows a custom button on the pane. """ +AUI_BUTTON_CUSTOM8 = 208 +""" Shows a custom button on the pane. """ +AUI_BUTTON_CUSTOM9 = 209 +""" Shows a custom button on the pane. """ + +# Pane Insert Level +AUI_INSERT_PANE = 0 +""" Level for inserting a pane. """ +AUI_INSERT_ROW = 1 +""" Level for inserting a row. """ +AUI_INSERT_DOCK = 2 +""" Level for inserting a dock. """ + +# Action constants +actionNone = 0 +""" No current action. """ +actionResize = 1 +""" Resize action. """ +actionClickButton = 2 +""" Click on a pane button action. """ +actionClickCaption = 3 +""" Click on a pane caption action. """ +actionDragToolbarPane = 4 +""" Drag a floating toolbar action. """ +actionDragFloatingPane = 5 +""" Drag a floating pane action. """ + +# Drop/Float constants +auiInsertRowPixels = 10 +""" Number of pixels between rows. """ +auiNewRowPixels = 40 +""" Number of pixels for a new inserted row. """ +auiLayerInsertPixels = 40 +""" Number of pixels between layers. """ +auiLayerInsertOffset = 5 +""" Number of offset pixels between layers. """ +auiToolBarLayer = 10 +""" AUI layer for a toolbar. """ + +# some built in bitmaps + +if wx.Platform == "__WXMAC__": + + close_bits = "\xFF\xFF\xFF\xFF\x0F\xFE\x03\xF8\x01\xF0\x19\xF3\xB8\xE3\xF0" \ + "\xE1\xE0\xE0\xF0\xE1\xB8\xE3\x19\xF3\x01\xF0\x03\xF8\x0F\xFE\xFF\xFF" + """ Close button bitmap for a pane on wxMAC. """ + +elif wx.Platform == "__WXGTK__": + + close_bits = "\xff\xff\xff\xff\x07\xf0\xfb\xef\xdb\xed\x8b\xe8\x1b\xec\x3b\xee" \ + "\x1b\xec\x8b\xe8\xdb\xed\xfb\xef\x07\xf0\xff\xff\xff\xff\xff\xff" + """ Close button bitmap for a pane on wxGTK. """ + +else: + + close_bits = "\xff\xff\xff\xff\xff\xff\xff\xff\xcf\xf3\x9f\xf9\x3f\xfc\x7f\xfe" \ + "\x3f\xfc\x9f\xf9\xcf\xf3\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" + """ Close button bitmap for a pane on wxMSW. """ + +pin_bits = '\xff\xff\xff\xff\xff\xff\x1f\xfc\xdf\xfc\xdf\xfc\xdf\xfc\xdf\xfc' \ + '\xdf\xfc\x0f\xf8\x7f\xff\x7f\xff\x7f\xff\xff\xff\xff\xff\xff\xff' +""" Pin button bitmap for a pane. """ + +max_bits = '\xff\xff\xff\xff\xff\xff\x07\xf0\xf7\xf7\x07\xf0\xf7\xf7\xf7\xf7' \ + '\xf7\xf7\xf7\xf7\xf7\xf7\x07\xf0\xff\xff\xff\xff\xff\xff\xff\xff' +""" Maximize button bitmap for a pane. """ + +restore_bits = '\xff\xff\xff\xff\xff\xff\x1f\xf0\x1f\xf0\xdf\xf7\x07\xf4\x07\xf4' \ + '\xf7\xf5\xf7\xf1\xf7\xfd\xf7\xfd\x07\xfc\xff\xff\xff\xff\xff\xff' +""" Restore/maximize button bitmap for a pane. """ + +minimize_bits = '\xff\xff\xff\xff\xff\xff\x07\xf0\xf7\xf7\x07\xf0\xff\xff\xff\xff' \ + '\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff' +""" Minimize button bitmap for a pane. """ + +restore_xpm = ["16 15 3 1", + " c None", + ". c #000000", + "+ c #FFFFFF", + " ", + " .......... ", + " .++++++++. ", + " .......... ", + " .++++++++. ", + " ..........+++. ", + " .++++++++.+++. ", + " ..........+++. ", + " .++++++++..... ", + " .++++++++. ", + " .++++++++. ", + " .++++++++. ", + " .++++++++. ", + " .......... ", + " "] +""" Restore/minimize button bitmap for a pane. """ + +#---------------------------------------------------------------------- + +down_focus_single = PyEmbeddedImage( + "iVBORw0KGgoAAAANSUhEUgAAAB0AAAAgCAIAAABhFeQrAAAAA3NCSVQICAjb4U/gAAACaUlE" + "QVRIib2WvWsUQRjGn5mdnWxyTaqz9q+QlLnGToSgWAYDNjbpNCAGDGIvaRPbNJGQyiAEbK+w" + "sAo2qexyEhbxsvt+jMXc3u3liPfhmWeXnWVm9vc+vO/M7prVzTb+gxyA7Ye/nXPWWmvtXKBb" + "B9YBcM5lWZam6by4QNcBsNamaeq9d87NmWutdc59+NgGoKIizCwsxMTMFI8oZmZilzomZiFm" + "FWERaXbv7eyueO+TJEHM79LSkvfeWnv2qftgex2ASGDmkrUkKUspiIuCy5IL4qKQgnghdQVx" + "ScKsxCKiaH8lIu99NOwAEFGsG4Dv5xeiQYOKBBYVUWJlFhIVVmIlEZGQJKVIYBbWoKqqwQN5" + "nqdpuri42OMys6rGOG/X78yW0bXWNyLqcyyAEEIIYcYK3aB5Lazb4o5fsPc3ToFaloxBwMle" + "6+9Pjfd7stda6HR85+dCPC86Y6ETcQEcHz32eZ7meZrnx0ePJnlk0vwenm70r/PkTgWdjjuV" + "rnPPfvxaa+3NcL3GMaub7XdPtNFoZFn24tmX1/trAOLuM6aaFQwQYExAMPWNaUw1FW+eHj5/" + "dbfZbDYajY33F7e1L4gUA5uo3fd8AWbQH70bjGqEyxLq3LoMYhKCgakCIWZoLLdkMRE43Iy0" + "tWi9QOP8xoIFAyBUjF7dgOizb9iMhLmByxIAHbAGKYigUPX3hqog47hSvfCHfYRaDcNg3IzO" + "7GmydRaGi37zMujrut/9l58nijROQ9yd3ZXLy8urq6vZWFmW9f+Yhrje++XlZR2keDpZa4f+" + "H/pKkiR+/f9dDsDWgQW6QHcuxKg/ZbVtCjjzINkAAAAASUVORK5CYII=") +""" VS2005 focused docking guide window down bitmap. """ + +#---------------------------------------------------------------------- +down_single = PyEmbeddedImage( + "iVBORw0KGgoAAAANSUhEUgAAAB0AAAAgCAIAAABhFeQrAAAAA3NCSVQICAjb4U/gAAACY0lE" + "QVRIib2WwWrUUBSG/3tzc5s2m0JhXPsU0u1s3Lkpui4W3PgAuhAFi2/QbesTVEphwCIU3Hbh" + "wk2LG1fujJQgtMk55x4Xd2aS6VAzM479JyQhufnOz3/uzcQMBgP8BzkAeZ4756y11tqlQIui" + "cACcc1mWpWm6ZK61Nk1T771zbilcxBxiAs659x/OAAQJIswsLMTEzBR/UczMxC51TMxCzEGE" + "RaR39WB3b9N7nyTJkLu2tua9t9ZefLx69GYbgIgyc82hJqlrqYiriuuaK+Kqkop4JXUVcU3C" + "HIhFJODsCxF57xu/RBT7BuDb958SNGgQUZYgEogDs5AE4UAcSEREk6QWUWbhoCGEENQDZVmm" + "abq6ujrkMnMIIdZ5t31vsUC3+l+JaMyxAFRVVRds0C1azsS6O273hH24cwq0UjIGipP9/t+f" + "6vZ7st9fKQpf/FqJ28+iEzoTF8Dx0RNflmlZpmV5fPR4lkdmzffwdGe8XyZ3Luh83Ll0k3vx" + "4/dWf3+B/Q2OGQwGGxsbeZ5nWfbi2efXB1sA4uozZjRKDaAwRqGmvTCNGQ3F26eHz1/d7/V6" + "eZ6fn5/f1bogCmhsonU+9AWY5nr0bjCtKS6LtrltGcQQ1MCMCiEm1MmtWUwETh6mjq1qw0Jd" + "fmPD1ADQEWPYNyD6HBs2U2Vu4bIoEBpWE0EE6ej68NaoSBdXRi/8SR/a6qE29830yKFmm2c6" + "2fTbp8FYN/0evPw0U6UuTXB39zYvLy+vr68XY2VZNv5imuB679fX10MT8Xyy1k58P4yVJEn8" + "9/93OQBFURRFsRTcWH8An5lwqISXsWUAAAAASUVORK5CYII=") +""" VS2005 unfocused docking guide window down bitmap. """ + +#---------------------------------------------------------------------- +left_focus_single = PyEmbeddedImage( + "iVBORw0KGgoAAAANSUhEUgAAACAAAAAdCAIAAABE/PnQAAAAA3NCSVQICAjb4U/gAAACVElE" + "QVRIibWWvW8TQRDF3+7Ors8ShSsaSpo0dEgoFcINVChSBFRUkajpIKKgiPgP0pqGJiAhITqE" + "FIk2BQUVHT2VK+y7ndmhWN/5Ixcbh8tYWvtO8vvdm5mdPXPv+RmuMgjA670/RGSttdZ2q354" + "YgkAERVF4b3vHABMCIC11nsfQiCiqwJYa4noxbNvOw/6AJIk62ySJMLMwhI5MnPMnxzMzJHJ" + "E0dmicxJhEXk+uTO0fFuCME5h1yDxbh5+zEz93q+LGOv50WUmStOVZSqkjJyWXJVcRm5LKWM" + "3PNURq6iMKfIIpJw9n08Hg8Gg36/3wL4+eu3iHpykcWTS5pElCWJpMiJWaIk4RQ5RRERda4S" + "UWbhpCmllDQA0+k0pZQFVwF3bzEAZ5N3jgje+0COnPVknbUAdm5cW5/1/eGPxcuL2saoAczC" + "DQWAV0/fr1c/HxcBFNC8QGEMMu3NuyddAfIjG9QLjKJTB3NIHV050EZuoQI6+93q4P7B6TYA" + "A2gW1xlC61K0OXi492HZ6EbAnGFqEmBmhlYc7A9HutRq/wgA5plSwDT9tORgfzgCNsmv2QfQ" + "OvEwps7BooOPpwebxFsB83wazdWdl321BjOGWWejrciZ0+wBMwef76LPnx6trXFrivIfVOsl" + "P2V7FwH4MhpuCTBLX7mjckU628naTImlrdDdLDJ59OT+XDDU8SwyTX+Y2bC7hIPVA+fty6/b" + "SmwBODreHY/H0+n0P0WLomjegJYAIYTBYNAcp5cOa20IoQXgnMuvAh0GATg8scAEmHQrneMv" + "3LAo6X/e0vAAAAAASUVORK5CYII=") +""" VS2005 focused docking guide window left bitmap. """ + +#---------------------------------------------------------------------- +left_single = PyEmbeddedImage( + "iVBORw0KGgoAAAANSUhEUgAAACAAAAAdCAIAAABE/PnQAAAAA3NCSVQICAjb4U/gAAACTklE" + "QVRIibWWPWsUURSG3/u5u8RiKxtLmzR2gqQSttFKhKBWqQL+BQXL4D9IGxsrBUGEFCIEbC0s" + "rOzshYHt3Jl7PizuzsduJhs3Ts7C3Z2BfZ95zzn33DGnp6e4zvAAdnZ2vPfWWmvtsOpFUXgA" + "3vvxeBxCuC6AtTaEEGP03g8LQE5RTo73/sXzr7sPJwCExTorLMxExMSJEhGl/MlBRJTIB0+J" + "iBORMBMz3/xz7+h4L8bonFsCunH77lMiGo1CWabRKDArEVUkVeKq4jJRWVJVUZmoLLlMNAq+" + "TFQlJpJEzCz49n0+n0+n08lk0gP4+es3swbvEnHwTlSYlViYJZEQcWJhkkSSmJnVuYpZiZhE" + "RUREI7BYLESkTVE37t8hAM5KcM57hBCid97Z4K2zFsDurRubk74/+9G9vKhtjBrAdG4oALw6" + "eLdZ/XxcBFBA8wKFMci012+fDQXIj2xQLzCKQR20kDqGcqCNXKcCuvzd6+DB4dk2AANoFtcl" + "QutS9Dl49Pj9qtFLAS3D1CTALA2tOdifnehKq/0jAGgzpYBp+mnFwf7sBLhMfsM+gNaJhzF1" + "DroOPpwdXibeC2jzaTRXty37eg2WDLPJRl+RM6fZA6YFn++iTx+fbKxxb4ryH1TrJT9lfxcB" + "+Hwy2xJgVr5yR+WKDLaTtZkSK1thuFlk8ujJ/dkxNPAsMk1/mOWwu4KD9QPnzcsv20psATg6" + "3pvP54vF4j9Fx+Nx8wa0AogxTqfT5ji9clhrY4w9AOdcfhUYMDyAoiiKohhWt4m/9Qss43IB" + "CBMAAAAASUVORK5CYII=") +""" VS2005 unfocused docking guide window left bitmap. """ + +#---------------------------------------------------------------------- +right_focus_single = PyEmbeddedImage( + "iVBORw0KGgoAAAANSUhEUgAAACAAAAAdCAIAAABE/PnQAAAAA3NCSVQICAjb4U/gAAACWElE" + "QVRIibWWv2/TQBTHv3e+uyRbJwZWFv4AJNSRLjChSkhlYqrEzFZVDAwVC3PXsrAUISTExlKJ" + "tQMSWzcmFqaqQqT2+8VwtuMkbiBp+mzF0pPz/dzX7z373IMXp7jJCABebf8JIXjvvffrVd8/" + "9gFACGE4HMYY1w4AxgGA9z7GmFIKIdwUwHsfQth7/vXuoxEAFfWFV1ERZhYWYmJmykcOZmbi" + "EAMTsxCzirCI3BrfPzjcTCkVRYFcg27cubfDzINBLEsaDKKIMXPFWpFUlZTEZclVxSVxWUpJ" + "PIihJK5ImJVYRBSn387Pzzc2NkajUQ/g7McvEYuhIJYYCjUVMRYVUWJlFhIVVmIlERErikrE" + "mIXVVFXVEnB5eamqWXAW8Gb39uKHevbzNwARZVFirUSIlFkqEVUD8Pb71P1Lt83LZ+8BAA7O" + "AYABMAPcFfcvDXj97ikA5wxmHVVrf64LyA7Mau1so770uVjRQa1lzaKtSc2ZWAR4uHsyn2xq" + "YBnjbFp4zsRCBw6Ptz/M5GoHgLla15AfUV8F/gEwA/Bk66jPgXNwMNhkyf199F816DIaB5bx" + "yB2aO2qFLsp/+Xiy22YmczA1Cq4hLQlwsK56xwHgumLWln0pgPv8aWcmNdVF7TKujkWAL0db" + "88nagXWb0xYgVn4XWf0CymdzWQNgapJzWC7HCnPQF5M5aBhXzthqgMkcoF57Zxx6YvaDMzO3" + "148pwMHhJhFdXFwQ0XVEh8NhuwOaAqSUUkoxxvaLulp471NKPYC80ci7gXVFALB/7IExMF6j" + "bht/AXIQRaTUgkiHAAAAAElFTkSuQmCC") +""" VS2005 focused docking guide window right bitmap. """ + +#---------------------------------------------------------------------- +right_single = PyEmbeddedImage( + "iVBORw0KGgoAAAANSUhEUgAAACAAAAAdCAIAAABE/PnQAAAAA3NCSVQICAjb4U/gAAACVElE" + "QVRIibWWv2sUQRTHvzM7M3dHmlQWtjb+AYKkTaOVCEKsrAL+CxaWwcY6bWysRAQRUtgEbC0E" + "u3RWNsJCCILZfb8sZvdu925zej/yveMWHnvvM9837+2OOz09xU0qANjZ2QkheO+999vNXpZl" + "ABBCGI/HMcabAnjvY4wppRDCdgHIJcrFCSG8eP7l7sMJABX1hVdREWYWFmJiZsqfLGZm4hAD" + "E7MQs4qwiNz6c//oeC+lVBRFA+jqzr0DZh6NYlXRaBRFjJlr1pqkrqUiriqua66Iq0oq4lEM" + "FXFNwqzEIqL4+u3i4mJ3d3cymQwAzn/8ErEYCmKJoVBTEWNRESVWZiFRYSVWEhGxoqhFjFlY" + "TVVVLQFXV1eqOitRV68Pby+v6fnP3wBElEWJtRYhUmapRVQNwJvvvftXbpuXz94BABycAwAD" + "YAa4a+5fGfDq7VMAzhnMOllt+rMpIDswa3JnG81lyMWaDppc1i7a2tCCiWWAB4dni8F2Dyxj" + "nPUTL5hY6sDh0eP3c7HGAWCuyWvIJRragX8AzAA82T8ZcuAcHAw2W/JwH/3XHnQZrQPLeOQO" + "zR21Rhflv3w4O5xGZnPQGwXXklYEOFg3e8cB4LrJbLrtKwHcp48Hc6FeF02Xcb2WAT6f7C8G" + "GwfWbU5bglj7WWTNAyh/28sWAL1JzrK8HWvMwZBmc9Ayrp2x9QCzOUCz9s44DGj+hTM3t5ur" + "Bzg63iOiy8tLItok6Xg8np6AeoCUUkopxjh9o64n731KaQCQDxr5NLAtBQBlWZZlucWkXf0F" + "imtJnvbT2psAAAAASUVORK5CYII=") +""" VS2005 unfocused docking guide window right bitmap. """ + +#---------------------------------------------------------------------- +tab_focus_single = PyEmbeddedImage( + "iVBORw0KGgoAAAANSUhEUgAAAB0AAAAdCAIAAADZ8fBYAAAAA3NCSVQICAjb4U/gAAAC10lE" + "QVRIidWWT0gUcRTH387+ZveXZgzsbmvSsmqEfzBJSYz+gUsHCYJgISPytCQKFhJdpIN0qIUO" + "ezIUaU/roQ5eEzp46ZT/DhG4haCXdSUPK+Wuzvze+02HgdFmFqMtD76Bx8yb3/v8vr/3Zn4z" + "np6ReTgCYwAwdqfEGFMURVGU/wIdfaswAGCMcc5VVf1fXIBdBgCKoqiq+nxkobn3BABIkgBA" + "hIiEJFAgorAOyxARBTKVoUAkgSiJkIhO73a/nLjGOd/nWkrPXbqLiH6/CgBEJiIaKA1BhkG6" + "QF1Hw0BdoK6TLtCvMl2gIQhRCiQiCfPLm5ubtbW1YNXXtuzadyJTZV4AkKYkMpEkkRQoEUmQ" + "JJQCpSAiMr1eg8hEJJSmlFJK0wdQLBYR0cl9laj7l6LGY5/tc2ejsrmdgeGJbG5nYHgym9uJ" + "x9KHeGuMNd7B8fSMzCfvyerq6rHHn2bmEgPDE09G+/9WaSqZmRofisfSiadnotHoozclp94K" + "oGWznNxn/e8q4LqznNwXmb4KuO6s4643lZyugOvOcj8PDyrgurOOe30r05tKZv7ALavXmszt" + "rXZZL7EjhTmuU8lpRxNSyemZuUEAmJlLOPzU+CAAuKFluO7OWpF4LO1OPsTcejOOTcRepqXR" + "tngs7Y6U4bbcqNrIF6bGh6yt0prAgm7kC6E2fSNfWF9b2d7e1jStvqGlbMSmeRsuP7zZZvp8" + "PvCoW1s/a2qq7vddD57y3b7VZfmNfGFxadUQBgqztbWps7Pdy04uLq0WSyVJnoMRgUY45NM0" + "bXZZ7OvtaA8vLOdeT85mP+4eXN35K/6W5nBjxFz5tv7+w8LWF3+oTW+IBpsavStf1+xIfTTY" + "cNbknDPGfqsD5/xCa6AuDFe791xtEJyHIhHedTGw17tnj49EeFdH8GAkEAhwzgF+7HMZY5qm" + "cc6tD6rDGGOMMUS075aN2Ho9R/R/9gsXZ7dKHM+ODQAAAABJRU5ErkJggg==") +""" VS2005 focused docking guide window center bitmap. """ + +#---------------------------------------------------------------------- +tab_single = PyEmbeddedImage( + "iVBORw0KGgoAAAANSUhEUgAAAB0AAAAdCAIAAADZ8fBYAAAAA3NCSVQICAjb4U/gAAAC1klE" + "QVRIidWVTWgTQRTHJ5vZZEyMLKSx1RbSImJbioeiCKIechLBU8CCtadAaaGIFC/iQTxowENO" + "hUohp/Tiocdce/Gk/TiIpTm0JCnmA+OaFJs2u/PerIcp27IbKsZ66Ft47P5n3m/evLc768lm" + "s+Q/GCWEBINBSqmiKIqinApU13VKCKGUMsZUVT1lrqIoqqq+frYyeP8cIUSgIIQgAgACcuAA" + "wOUlDQCAA1UpcADkAAIREPHiwa2383cYY0TWwa7AlRuPAMDvVwkhiBYAmCBMjqaJBgfDANME" + "g4NhoMHBr1KDg8kRQHBAREE+r1er1Z6enkOubbn8d0RLpV5CiLAEogUoEAUHAYAcBYLgIDgi" + "ouX1mogWAIKwhBBCWD5Cms0mADi57xKX/6Ws8dgX+97ZqFxpb3JmPlfam5x5nyvtxWPpE7yc" + "I+c7OJ5sNhsOh4PB4Kunn5aWE5Mz87MvJv4201QyszA3HY+lE88vRaPRYrHozLcDaNsoJ/fl" + "xIcOuO4oJ/dNZqwDrjvqrOebSi52wHVHud+HJx1w3VFnvb6d5ZtKZv7AbZuvXMztZbvkR+wI" + "oY7nVHLR0YRUcnFpeYoQsrSccPiFuSlCiBvahuvurFTisbQ7+ARz55txHCL2NmWOtsVjabfS" + "hjt0L1Cu1BfmpuVRKReQ0HKlHhkxypV6Ib/ZaDQ0TesfGGqr2DTv+Ph4IBDw+XzEo9Zqv0Kh" + "wOOxu10XfA8f3JS+XKmvrm2Z3ARuDQ9fGx297qXnV9e2mvv7Aj3HFQ5md8Snadru7u7Rua6q" + "6sp6aTNXzX08OL67q7f9Q4PdTP1ZKCn5Qq321R8ZMQaiXf19VuGbJ1/8IZX+aNdAnxWJRHp7" + "e7e3t4+4oVCo0Wjout5qtdx9YIwxxlqtlj3aVgmHw5qmbWxsHNWXUqppGmNM/lCd/aWUUgoA" + "9mhbhTFGKT3sm67ruq7v7Oy4cR3bb5uW079be13FAAAAAElFTkSuQmCC") +""" VS2005 unfocused docking guide window center bitmap. """ + +#---------------------------------------------------------------------- +up_focus_single = PyEmbeddedImage( + "iVBORw0KGgoAAAANSUhEUgAAAB0AAAAgCAIAAABhFeQrAAAAA3NCSVQICAjb4U/gAAACTUlE" + "QVRIic2WP28TMRjGH/85c1miqEF8CnbUgSFdukVRmZGQ+gW6Vgwd+hW60Yq1gMQMQzpHGZAg" + "C6JS+QIMmUju/L5+Ge6ulzoRTcMh5TmdZfv8/vT4Pcu26h2N8R9kAZwMfltrtdZa60agx5fa" + "ArDWpmmaJElTXGBmAWitkyRxzllrG+Zqra21bz+OAQQOzETExJ48EfniKURE5MkmljwRe6LA" + "TMz8ZPbs9GzXOWeMQZHfW33/NOufvALALESUU8g95zlnnrKM8pwyT1nGmadHic085Z6Jgidm" + "Dhh/mU6nnU6n1WrFXAA/fv7iIEECsxAH5uApELHnwBQ8Bc/MLMbkzELEFCSEEII4YD6fhxAK" + "Tsx9/tQDEIgqOzRggAQQQEEBguIFgKoNqDdfvy1yYq41emG4QKkSpDQAiNQfFQClpBoZcaK2" + "s0awEHzXVVyri1gxN7FaFuILu6qwtAyokqWWwEvcxNTTKsIK95Cqs4JJzV02vMJvHS/1cFFQ" + "UGV+K3tSzWlZq/5bOWGllIio0mzpX+pZSJXdVRmOuabcItRC+ZfKcn+pFRvN65fvNihj9Y7G" + "o9FoMplcX18f9M5lUx30zofD4WQyubm56R2Nm9oYY20B98XeRfPcAro+ei1uf/DBt9u+3c7b" + "7f7gfTPc/cOr7HE36+5k3Z28u5N1u/uHV/dG3X+gfb7YW8dgpC1YD1vBjfP7oEW6Lvf0bHc6" + "nc7n881YaZre3pjucJ1znU7n9qx+qLTWzrkVXGNMcav4d1kAx5camAGzRoiF/gCKPmudbgYP" + "HQAAAABJRU5ErkJggg==") +""" VS2005 focused docking guide window up bitmap. """ + +#---------------------------------------------------------------------- +up_single = PyEmbeddedImage( + "iVBORw0KGgoAAAANSUhEUgAAAB0AAAAgCAIAAABhFeQrAAAAA3NCSVQICAjb4U/gAAACTklE" + "QVRIic2WP4vUQBjGn/mTMUtgWS6yvb29XGGzzXXHwdWCcGBzH8BCweK+wnWyWKtot6DNge0V" + "gjYnNn4BA9vdJvO+81ok2ewmi7d3RtgnZEgm8/545skwiZrNZvgPsgCSJLHWaq211r1Asyyz" + "AKy1cRxHUdQzV2sdRZFzzlrbCxdlDmUC1to3Hy8BBA7MRMTEnjwR+fIoRUTkyUaWPBF7osBM" + "zDy+fnR2vu+cM8ZU3KV+fLo+fPUUALMQUUGh8FwUnHvKcyoKyj3lOeee7kU291R4JgqemDng" + "8ut8Ph+NRoPBoM0F8PPXbw4SJDALcWAOngIRew5MwVPwzMxiTMEsRExBQgghiAMWi0UIoclh" + "VY8fegACUVWHBgwQAQIoKEBQngBQ3wPq9bfv7XzX7o1eGS5QqgIpDQAizUMFQCmpR3bf26qc" + "NYKV4nVX7aumaavNjayWlfrSriotdQF1WKoD7nAj00yrLCvdQ+rOGiYNt2t4g9+mXprhoqCg" + "qnxre1LPqatN762asFJKRFRltvIvzSykTndTwm2uqbYItdL+5aLbX2nDRvPiyds7tC2p2WyW" + "pmmSJHEcP3/25cPFSXfQNjqeTE9fPhiPx0mSXF1d9bMxdrUD3OPJtH9uCd0evRX38Oi9Hw79" + "cFgMh4dH7/rhHpxc5PfTPN3L070i3cvT9ODk4saqmz9on6eTbQy2tAPrYSe47XxvtUi35Z6d" + "78/n88VicTdWHMfLP6Y1rnNuNBotv9W3ldbaObeBa4wp/yr+XRZAlmVZlvWCW+oP2FUt8NYb" + "g5wAAAAASUVORK5CYII=") +""" VS2005 unfocused docking guide window up bitmap. """ + +#---------------------------------------------------------------------- +down = PyEmbeddedImage( + "iVBORw0KGgoAAAANSUhEUgAAAB0AAAAgCAIAAABhFeQrAAAAA3NCSVQICAjb4U/gAAACFUlE" + "QVRIidWVPWvbUBSG3+tv8KKpnYtH/4DuJtCti2nntBm7depQWih0zT9w/AtSQsDQ0JIfkKFD" + "wRC61Iu3uBgRiKXz1eFalizb8QfxkFdCurofz3l1zhVyg8EAe1BhH9BHyC3NWkTU/XYFQEVF" + "mFlYiImZyR9ezMzEpXKJiVmIWUVYRJ7cPT/uHizhFgqF6+93Lz8fAhAxZo5ZY5I4log4ijiO" + "OSKOIomIq+VSRByTMCuxiCiufo3H4yAI8txisQjgz98bUVNTEWNRESVWZiFRYSVWEhGxYjEW" + "MWZhNVVVtQoQhuESrtfXw6e7JbTd+k1E6dv3+/3dQPeo3+8/3n22Si+OLgFLn52D4aLTun/V" + "er8XnVZ1NKqM/lX9eTNaC92IC+D87HUlDMthWA7D87NXmyzZNL+nl0ez60Nyt4Jux91Kee71" + "8Lbd6uxwzXFcr9drNpv+4f2bn59O2gDMAMC5ZJY5wOCcwZxlV7tkKr68PX338Vmj0cBev7f8" + "d0GkSG0i0576Alza7707LGqBy2JZblYOPgnm4JJA8Blay41ZnAfO3xbumWjTQOv8+oKZA2AJ" + "Y1o3wPucGXYLYVZwWQzQlJWmwIMs6Z8OJUHWcUU1aWZ9WKaGlo67xZlTbbbPbL7oq7fBTHm/" + "Jx9+bBRpnea4x92D4XA4mUx2Y9VqteVcAEEQ1Ov13bhZ5fP7IFB4v/v41f8HFQ1ap0nfm7YA" + "AAAASUVORK5CYII=") +""" VS2005 unfocused docking guide window down bitmap. """ + +#---------------------------------------------------------------------- +down_focus = PyEmbeddedImage( + "iVBORw0KGgoAAAANSUhEUgAAAB0AAAAgCAIAAABhFeQrAAAAA3NCSVQICAjb4U/gAAACaUlE" + "QVRIib2WvWsUQRjGn5mdnWxyTaqz9q+QlLnGToSgWAYDNjbpNCAGDGIvaRPbNJGQyiAEbK+w" + "sAo2qexyEhbxsvt+jMXc3u3liPfhmWeXnWVm9vc+vO/M7prVzTb+gxyA7Ye/nXPWWmvtXKBb" + "B9YBcM5lWZam6by4QNcBsNamaeq9d87NmWutdc59+NgGoKIizCwsxMTMFI8oZmZilzomZiFm" + "FWERaXbv7eyueO+TJEHM79LSkvfeWnv2qftgex2ASGDmkrUkKUspiIuCy5IL4qKQgnghdQVx" + "ScKsxCKiaH8lIu99NOwAEFGsG4Dv5xeiQYOKBBYVUWJlFhIVVmIlEZGQJKVIYBbWoKqqwQN5" + "nqdpuri42OMys6rGOG/X78yW0bXWNyLqcyyAEEIIYcYK3aB5Lazb4o5fsPc3ToFaloxBwMle" + "6+9Pjfd7stda6HR85+dCPC86Y6ETcQEcHz32eZ7meZrnx0ePJnlk0vwenm70r/PkTgWdjjuV" + "rnPPfvxaa+3NcL3GMaub7XdPtNFoZFn24tmX1/trAOLuM6aaFQwQYExAMPWNaUw1FW+eHj5/" + "dbfZbDYajY33F7e1L4gUA5uo3fd8AWbQH70bjGqEyxLq3LoMYhKCgakCIWZoLLdkMRE43Iy0" + "tWi9QOP8xoIFAyBUjF7dgOizb9iMhLmByxIAHbAGKYigUPX3hqog47hSvfCHfYRaDcNg3IzO" + "7GmydRaGi37zMujrut/9l58nijROQ9yd3ZXLy8urq6vZWFmW9f+Yhrje++XlZR2keDpZa4f+" + "H/pKkiR+/f9dDsDWgQW6QHcuxKg/ZbVtCjjzINkAAAAASUVORK5CYII=") +""" VS2005 focused docking guide window down bitmap. """ + +#---------------------------------------------------------------------- +left = PyEmbeddedImage( + "iVBORw0KGgoAAAANSUhEUgAAACAAAAAdCAIAAABE/PnQAAAAA3NCSVQICAjb4U/gAAACMElE" + "QVRIib2WPYsTURSG3zv3ThKJRSqblDYLwU6wFMKCViIEtbKQ/QdWgrXt/oO4hZWCIEIKUfYH" + "WFgIATuraewHM3PPh8XNZCaTSWI2oydwMx/kfeY959wzMbPZDC3FaDTavOi23Wgron8n/Z8A" + "rnry/NmXk/vXAAhLZCNhYSYiJvbkiciHTwgiIk8uduSJ2BMJMzHzjd93zi9OmwEAbt5+TETd" + "bpxlvtuNmZWIcpLcc55z5inLKM8p85RlnHnqxi7zlHsmEk/MLPj6LUmS4XDYDPjx8xezxs56" + "4thZUWFWYmEWT0LEnoVJPIlnZlZrc2YlYhIVERHtAIvFYquDu7cIgI0kttY5xHHccdbZKHaR" + "jSIAJ8Pru5M+GX+vnm4rslEDmMoFBYCXT9/uVt+MbQAFNCxQGINAe/XmSVuA8MgGxQKjaNVB" + "CSmiLQe6kqtUQJfHjQ7unV0eAjCABnFdIrQoRZODBw/frRvdCygZpiABZmmo5mAynupaq/0l" + "ACgzpYBZ9dOag8l4CuyT37EPoEXiYUyRg6qD95dn+8QbAWU+jYbqlmWv12DJMLtsNBU5cFZ7" + "wJTgzS76+OHRzho3pij8QLVYwlM2dxGAT9PxgQCz9hU6KlSktZ2sqymxthXam0UmjJ7QnxVD" + "Lc8is+oPsxx2V3BQf+G8fvH5UIkDAOcXp0mSVF94V4ter5emab/frwMADAaDcOOYSNO00+mE" + "4zrgePWaiAMwn8+PF932//MPv0Uk8OspzrYAAAAASUVORK5CYII=") +""" VS2005 unfocused docking guide window left bitmap. """ + +#---------------------------------------------------------------------- +left_focus = PyEmbeddedImage( + "iVBORw0KGgoAAAANSUhEUgAAACAAAAAdCAIAAABE/PnQAAAAA3NCSVQICAjb4U/gAAACVElE" + "QVRIibWWvW8TQRDF3+7Ors8ShSsaSpo0dEgoFcINVChSBFRUkajpIKKgiPgP0pqGJiAhITqE" + "FIk2BQUVHT2VK+y7ndmhWN/5Ixcbh8tYWvtO8vvdm5mdPXPv+RmuMgjA670/RGSttdZ2q354" + "YgkAERVF4b3vHABMCIC11nsfQiCiqwJYa4noxbNvOw/6AJIk62ySJMLMwhI5MnPMnxzMzJHJ" + "E0dmicxJhEXk+uTO0fFuCME5h1yDxbh5+zEz93q+LGOv50WUmStOVZSqkjJyWXJVcRm5LKWM" + "3PNURq6iMKfIIpJw9n08Hg8Gg36/3wL4+eu3iHpykcWTS5pElCWJpMiJWaIk4RQ5RRERda4S" + "UWbhpCmllDQA0+k0pZQFVwF3bzEAZ5N3jgje+0COnPVknbUAdm5cW5/1/eGPxcuL2saoAczC" + "DQWAV0/fr1c/HxcBFNC8QGEMMu3NuyddAfIjG9QLjKJTB3NIHV050EZuoQI6+93q4P7B6TYA" + "A2gW1xlC61K0OXi492HZ6EbAnGFqEmBmhlYc7A9HutRq/wgA5plSwDT9tORgfzgCNsmv2QfQ" + "OvEwps7BooOPpwebxFsB83wazdWdl321BjOGWWejrciZ0+wBMwef76LPnx6trXFrivIfVOsl" + "P2V7FwH4MhpuCTBLX7mjckU628naTImlrdDdLDJ59OT+XDDU8SwyTX+Y2bC7hIPVA+fty6/b" + "SmwBODreHY/H0+n0P0WLomjegJYAIYTBYNAcp5cOa20IoQXgnMuvAh0GATg8scAEmHQrneMv" + "3LAo6X/e0vAAAAAASUVORK5CYII=") +""" VS2005 focused docking guide window left bitmap. """ + +#---------------------------------------------------------------------- +right = PyEmbeddedImage( + "iVBORw0KGgoAAAANSUhEUgAAACAAAAAdCAIAAABE/PnQAAAAA3NCSVQICAjb4U/gAAACMklE" + "QVRIibWWvY7TUBCFz83vojSuKNiShgdAol8hQYWQkJaKAuUNtqWmoeANFgoqhJAQHRLaB6BA" + "orC0HWnSUEURK2LPmRmKayeO4wTysydWbI+c+e7xzDgOo9EIK0rTdDW4m0Ij4FBK07R1fdmj" + "rh3QqZ6cPf965+ENAKbWardMTZWkUoVCUuIniiSFnW6HQqqQpkpVvfnn3uu395sBAG7fPSXZ" + "73ezTPr9rqqTzGm5aJ5rJswy5jkzYZZpJux3O5kwFyVNqKqGb9/H4/Hx8XEz4PLnL1XvdtpC" + "7Xba5qbqVFM1oZEqakoTmqiqerudqzqpNDczM+8Bs9lsrYNXw1ub7+nl+DcAVaOa0HJVESM1" + "VzVzAG9+LF2/dZFfPHsPAAgIAQAcgDsQ1ly/NeDlu6cAQnC4V7L6/GtfQHTgXuSONopdk4sd" + "HRS5vFy0l6EVE5sAD4YXq8GyBh4xwZcTr5jY6CDg0eMPtVjhAPBQ5HXEW9RUgX8A3AE8OTlv" + "chACAhy+WHJzH/1XDaqM0oFHPGKHxo7aoYviTz5eDOeRxRwsjUIoSVsCAryaveIACNVkPi/7" + "VoDw+dNpLbTURfNlrNcmwJfzk9Vg4cCrzekbEDs/i7x4AMWt3B0AsDTJUR7LscMcNGkxByVj" + "7YztBljMAYq1V8ahQfU/nNrc7q/6e9FkMplOpyKyT9Kjo6MkSQaDQZqmdQdJkiRJsk92AFdX" + "V71eLx7XAQfRYDCYH68FHOr19C8Ad0k9S0aHzwAAAABJRU5ErkJggg==") +""" VS2005 unfocused docking guide window right bitmap. """ + +#---------------------------------------------------------------------- +right_focus = PyEmbeddedImage( + "iVBORw0KGgoAAAANSUhEUgAAACAAAAAdCAIAAABE/PnQAAAAA3NCSVQICAjb4U/gAAACWElE" + "QVRIibWWv2/TQBTHv3e+uyRbJwZWFv4AJNSRLjChSkhlYqrEzFZVDAwVC3PXsrAUISTExlKJ" + "tQMSWzcmFqaqQqT2+8VwtuMkbiBp+mzF0pPz/dzX7z373IMXp7jJCABebf8JIXjvvffrVd8/" + "9gFACGE4HMYY1w4AxgGA9z7GmFIKIdwUwHsfQth7/vXuoxEAFfWFV1ERZhYWYmJmykcOZmbi" + "EAMTsxCzirCI3BrfPzjcTCkVRYFcg27cubfDzINBLEsaDKKIMXPFWpFUlZTEZclVxSVxWUpJ" + "PIihJK5ImJVYRBSn387Pzzc2NkajUQ/g7McvEYuhIJYYCjUVMRYVUWJlFhIVVmIlERErikrE" + "mIXVVFXVEnB5eamqWXAW8Gb39uKHevbzNwARZVFirUSIlFkqEVUD8Pb71P1Lt83LZ+8BAA7O" + "AYABMAPcFfcvDXj97ikA5wxmHVVrf64LyA7Mau1so770uVjRQa1lzaKtSc2ZWAR4uHsyn2xq" + "YBnjbFp4zsRCBw6Ptz/M5GoHgLla15AfUV8F/gEwA/Bk66jPgXNwMNhkyf199F816DIaB5bx" + "yB2aO2qFLsp/+Xiy22YmczA1Cq4hLQlwsK56xwHgumLWln0pgPv8aWcmNdVF7TKujkWAL0db" + "88nagXWb0xYgVn4XWf0CymdzWQNgapJzWC7HCnPQF5M5aBhXzthqgMkcoF57Zxx6YvaDMzO3" + "148pwMHhJhFdXFwQ0XVEh8NhuwOaAqSUUkoxxvaLulp471NKPYC80ci7gXVFALB/7IExMF6j" + "bht/AXIQRaTUgkiHAAAAAElFTkSuQmCC") +""" VS2005 focused docking guide window right bitmap. """ + +#---------------------------------------------------------------------- +tab = PyEmbeddedImage( + "iVBORw0KGgoAAAANSUhEUgAAAB0AAAAdCAIAAADZ8fBYAAAAA3NCSVQICAjb4U/gAAACq0lE" + "QVRIidWWTWgTQRTHJ+3ETEugERrtF6QhFhKsIlgQRITm5LkBvdiDhBZKc5DiRXryoAEPPRUi" + "pTnVi4cee5NePAitFtFoVxSyheYDa02KCd3deW/Gw2Cy7MZaIz307TK7/Gfeb9587Nvx6LpO" + "TsA6TgJ6glyqHgcHB4/ub0ZvdRFCBApCCCIAICAHDgBcXcoAADhQLwUOgBxAIAIinju89iRz" + "gzHW5Pb09BBCImO3AcDn8xJCECUAWCAsjpaFJgfTBMsCk4NposnB56UmB4sjgOCAiIJsbJXL" + "5b6+PsYYtQev5b8hSi/tJIQIKRAloEAUHAQAchQIgoPgiIiys9NClAAIQgohhJBnCKnX6wDQ" + "jFfZ0+TA/8xpIv6+8e5cN61Qm05ltEJtOvVMK9QS8ewRpWqj2js4nsb+nbv3cnU9OZ3KzD2c" + "/NdIF9IrS4sziXg2+aA/FAr5/X5nvG1AW3o5ufOTL9rgur2c3Mcrd9rgur1Oe7wL6edtcN1e" + "7v1wtw2u2+u0z2978S6kV/7CbRmv6sxdquVSH7HTR/9tE+PLUsqp2cz27k/7PTWbkcezifHl" + "tbW1XC6n6zp1dONeWaUk4tnjTwtx5F81KEcSaQxzdT1p1xPxrFtpwY3d7C6WKkuLMypVqg4U" + "tFiqBEfNYqmi57er1WogEBgOx1oqDVoz/77e2Onu6hq7emGg/6w9imKp8ubt149a/mI0rGqV" + "8u7DlyuXRuzKp8/5yzG/yr9NrmEYm1uFba2svTq0c0eu+2LR88z7Qy905PW9vZwvOGqGQ73D" + "Q1Lf9eR3vitlONQbHpLBYHBwcJAx5rGfd6rV6v7+vmEY7nVgjDHGDMNo1LZUIpGIc34ppYFA" + "gDGmfqgOo5RSSgGgUdtSUSUANLmqWp0q/mTK82hFcX4Bm24GMv+uL+EAAAAASUVORK5CYII=") +""" VS2005 unfocused docking guide window center bitmap. """ + +#---------------------------------------------------------------------- +tab_focus = PyEmbeddedImage( + "iVBORw0KGgoAAAANSUhEUgAAAB0AAAAdCAIAAADZ8fBYAAAAA3NCSVQICAjb4U/gAAAC10lE" + "QVRIidWWT0gUcRTH387+ZveXZgzsbmvSsmqEfzBJSYz+gUsHCYJgISPytCQKFhJdpIN0qIUO" + "ezIUaU/roQ5eEzp46ZT/DhG4haCXdSUPK+Wuzvze+02HgdFmFqMtD76Bx8yb3/v8vr/3Zn4z" + "np6ReTgCYwAwdqfEGFMURVGU/wIdfaswAGCMcc5VVf1fXIBdBgCKoqiq+nxkobn3BABIkgBA" + "hIiEJFAgorAOyxARBTKVoUAkgSiJkIhO73a/nLjGOd/nWkrPXbqLiH6/CgBEJiIaKA1BhkG6" + "QF1Hw0BdoK6TLtCvMl2gIQhRCiQiCfPLm5ubtbW1YNXXtuzadyJTZV4AkKYkMpEkkRQoEUmQ" + "JJQCpSAiMr1eg8hEJJSmlFJK0wdQLBYR0cl9laj7l6LGY5/tc2ejsrmdgeGJbG5nYHgym9uJ" + "x9KHeGuMNd7B8fSMzCfvyerq6rHHn2bmEgPDE09G+/9WaSqZmRofisfSiadnotHoozclp94K" + "oGWznNxn/e8q4LqznNwXmb4KuO6s4643lZyugOvOcj8PDyrgurOOe30r05tKZv7ALavXmszt" + "rXZZL7EjhTmuU8lpRxNSyemZuUEAmJlLOPzU+CAAuKFluO7OWpF4LO1OPsTcejOOTcRepqXR" + "tngs7Y6U4bbcqNrIF6bGh6yt0prAgm7kC6E2fSNfWF9b2d7e1jStvqGlbMSmeRsuP7zZZvp8" + "PvCoW1s/a2qq7vddD57y3b7VZfmNfGFxadUQBgqztbWps7Pdy04uLq0WSyVJnoMRgUY45NM0" + "bXZZ7OvtaA8vLOdeT85mP+4eXN35K/6W5nBjxFz5tv7+w8LWF3+oTW+IBpsavStf1+xIfTTY" + "cNbknDPGfqsD5/xCa6AuDFe791xtEJyHIhHedTGw17tnj49EeFdH8GAkEAhwzgF+7HMZY5qm" + "cc6tD6rDGGOMMUS075aN2Ho9R/R/9gsXZ7dKHM+ODQAAAABJRU5ErkJggg==") +""" VS2005 focused docking guide window center bitmap. """ + +#---------------------------------------------------------------------- +up = PyEmbeddedImage( + "iVBORw0KGgoAAAANSUhEUgAAAB0AAAAgCAIAAABhFeQrAAAAA3NCSVQICAjb4U/gAAACHUlE" + "QVRIidWWP4vUQBjGn8mfcyGQYiM2W8mWsRcLm22uWw6uFpQt7WwVLPwKVsp+gFMsAwqynY2F" + "oLAgNu4HsEiz3E7mfee1yN9Lcueu7oo+IcPMZN4fz7yZzEQlSYIDyAMQx/F+ocvl0tkvsdKh" + "uF6z8eLsAwDLlpmImNiQISKTX7mIiAx5vkeGiA2RZSZmvnF++9nzO0EQ9HC/vj2fPr0PgFmI" + "KCObGc4y1oa0piwjbUhr1oau+Z42lBkmsoaY2eLjpzRN+7kAvn3/wVasWGYhtszWkCViw5bJ" + "GrKGmVlcN2MWIiYr1lpr5QjYbDb9eQBw95YBIBBVdDiAC/iAAAoKEOQ3AJRtQL38/OXS/ALw" + "XKcxXKBUAVIOAIjUDxUApaQcecV7A3DkuYJG8EVX7VpdtNXm+p4jjfjcrsotdQFlslQH3OH6" + "bj2tPCx3Dyk7S5jU3K7hHr91vNTDRUFBFfkt7Uk5p6763lsxYaWUiKjCbOFf6llImd2+DLe5" + "ruM0UlA5uazS7S/Usz88vnf2G2VLKkmSap989OD9m8WsO2gbnU7mD5/cHI/H+C/3yR24p5P5" + "/rk5dHv0VtzpyWsThiYMszCcnrzaD/d4ttDXIx0NdTTMoqGOouPZ4pdR7e+iq3fzyTYGWzrY" + "etj7zwOAOI7/yjmPHRfpFVKr1apqrNfrNE2bx+pOGgwGo9Eor1/wGwRB9QPwh/oH9oed9BPW" + "YyQlBOJt4AAAAABJRU5ErkJggg==") +""" VS2005 unfocused docking guide window up bitmap. """ +#---------------------------------------------------------------------- +up_focus = PyEmbeddedImage( + "iVBORw0KGgoAAAANSUhEUgAAAB0AAAAgCAIAAABhFeQrAAAAA3NCSVQICAjb4U/gAAACTUlE" + "QVRIic2WP28TMRjGH/85c1miqEF8CnbUgSFdukVRmZGQ+gW6Vgwd+hW60Yq1gMQMQzpHGZAg" + "C6JS+QIMmUju/L5+Ge6ulzoRTcMh5TmdZfv8/vT4Pcu26h2N8R9kAZwMfltrtdZa60agx5fa" + "ArDWpmmaJElTXGBmAWitkyRxzllrG+Zqra21bz+OAQQOzETExJ48EfniKURE5MkmljwRe6LA" + "TMz8ZPbs9GzXOWeMQZHfW33/NOufvALALESUU8g95zlnnrKM8pwyT1nGmadHic085Z6Jgidm" + "Dhh/mU6nnU6n1WrFXAA/fv7iIEECsxAH5uApELHnwBQ8Bc/MLMbkzELEFCSEEII4YD6fhxAK" + "Tsx9/tQDEIgqOzRggAQQQEEBguIFgKoNqDdfvy1yYq41emG4QKkSpDQAiNQfFQClpBoZcaK2" + "s0awEHzXVVyri1gxN7FaFuILu6qwtAyokqWWwEvcxNTTKsIK95Cqs4JJzV02vMJvHS/1cFFQ" + "UGV+K3tSzWlZq/5bOWGllIio0mzpX+pZSJXdVRmOuabcItRC+ZfKcn+pFRvN65fvNihj9Y7G" + "o9FoMplcX18f9M5lUx30zofD4WQyubm56R2Nm9oYY20B98XeRfPcAro+ei1uf/DBt9u+3c7b" + "7f7gfTPc/cOr7HE36+5k3Z28u5N1u/uHV/dG3X+gfb7YW8dgpC1YD1vBjfP7oEW6Lvf0bHc6" + "nc7n881YaZre3pjucJ1znU7n9qx+qLTWzrkVXGNMcav4d1kAx5camAGzRoiF/gCKPmudbgYP" + "HQAAAABJRU5ErkJggg==") +""" VS2005 focused docking guide window up bitmap. """ + +#---------------------------------------------------------------------- +aero_dock_pane = PyEmbeddedImage( + "iVBORw0KGgoAAAANSUhEUgAAAGcAAABlCAMAAABnVw3AAAAAAXNSR0IArs4c6QAAAARnQU1B" + "AACxjwv8YQUAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAA" + "AwBQTFRFAAAAb3WKdHqPdnyRd3+deYGge4OifISifoallZaWgIy1g463hZC5hZG6iJO8o6Sk" + "pKSkpKWlpaampqenqKmpqaqqqqurq6ysrKysra6urq+vr7CwsLGxsbKysrOzs7S0tLS0tLW1" + "tba2tre3t7i4uLm5uru7vLy8vL29vr6+iZfLjJrNjpzPjpzQkZ7PkJ/SlKHSkaHek6Pgk6Th" + "labjmKjlnqzhnqzjoa/npbPov8DAwMDAwMHBwsLCwsPDw8TExMXFxsbGycnJycrKysvLzMzM" + "zM3Nzc7Ozs/Pz9DQ0NDQ0NHR0dLS0tPT09TU1NTU1tbW1tfX0tTY19jY1tjd2NjY2dnZ2dra" + "2tvb29zc29zf3Nzc3N3d3d7e3t/fxs7szNPt0NXo0dfu1djk09js2tzk3d/k3d/m2Nzv3N/r" + "1Nr02N713OH13OL23+X43+X54ODg4eHh4eLi4+Pj4uPm4uPn4+Tk5OTk5OXl5ubm5+fn4eLo" + "4uTs5eXp5efv5+jo6Ojo6enp6urq6+vr6Onv7Ozs7O3t7e7u7u/v4Ob55Oj16Ov37e/26+/9" + "7/D28PDw8fHx8vLy8/Pz8/P09PT09fX19vb29vf38vT89ff9+Pj4+Pj5+vr6+vr7+/v8/Pz8" + "/f39/v7+////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAsPpcmgAAAQB0Uk5T////////////////////////////////" + "////////////////////////////////////////////////////////////////////////" + "////////////////////////////////////////////////////////////////////////" + "////////////////////////////////////////////////////////////////////////" + "////////////////////////////////////////////////////////////////////////" + "////////////////////AFP3ByUAAAAYdEVYdFNvZnR3YXJlAFBhaW50Lk5FVCB2My4zNqnn" + "4iUAAAf+SURBVGhD7Zr7d9tEGoa9UKBZCiapneBQN3bUQlgo13DbhXBrnRQnS0LJNhDbUO7s" + "ErCtZWWZm2Rj2YHdbWQsCSQL2/yjZkaypBlpdKH14XA4vL8kkuebx3PR6HtnHBtHkfxyZoGs" + "zMtR4sfjWJRi0mJRHZGlFhej1BCNs1XyocDbpa0ooEjtSfcDOHp6apyFAMzop4XfF2f0K7Vn" + "qpyhr0bT5AwHvhpOkzPQ/TVNjt73lT6c4jzQNV/1p8npq77SBlNsj6b4Sr1Ozmtr99xpvQpm" + "6LKvquyMVezOe9Ze81uDfNbRHLUtiPaqNi8KvrqqztvlRGGbypFJRM7mMoOtnPNHDV8JisOB" + "QczyJolE4qy/MMAX6Pl23VdNGeeMBi+sE0AEzsYl92tgXqj5ipNcnNHo0oYX5OVsnfe8beab" + "VVQcelHveTij895XrJdD6SGc4b+HCIglcHTK0yAPJ4dPAYOJtUd/78b3dAdE4owYz6zzcM4Q" + "3tEop/v18ePHv+7aICJndMbdIDdn93Iwp/vh7VAf2iAy5/KuC+TmPN0J5HQ+eNDUB51Ji8ic" + "zjMhnAwpIbT7TfjXU4+ZeuqfbRNE5miZEE5qGNSe2g//s/RDLYgzSoVwiKlammMmvVTjLE0w" + "NH+UJmV37peFe3yInM3NZp0liz/azk+NM0ptt3tkdXZSxGQ1vD3EDOpiep6s9EVSeW+y5e03" + "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" + "XFekjMjBOHUAAAAASUVORK5CYII=") + +#---------------------------------------------------------------------- +aero_dock_pane_denied = PyEmbeddedImage( + "iVBORw0KGgoAAAANSUhEUgAAAGcAAABlCAYAAABQif3yAAAAAXNSR0IArs4c6QAAAARnQU1B" + "AACxjwv8YQUAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAA" + "ABh0RVh0U29mdHdhcmUAUGFpbnQuTkVUIHYzLjM2qefiJQAAELhJREFUeF7tnfmPHNURx81v" + "hEsCIQQCxCnu60d+AH5BCCGBAAkEP/AHABKKkEhCCErCHcA4WGAIYJwYbOMrGHzgE9vYxvje" + "9drYa+99etd7zHov766PSn3avFFvu3v6dU9Pz2Q9K5Vmtt9VVd9X76zqOWtSCf4NDw9LfX39" + "pMbGxklHR0YKxuHvzj570tVXXz3ptttuO6tgjUykioeGhuTbRYukrr5eRkZG5OTJkwUj6qcd" + "2ptIOiyYLFV79khDQ0PBAPEDm/Zot2BCTZSKFy9eLGNjY6mCc+zYMaHdiaLDgsmxYOHCVIHB" + "kvij3YIJNVEqLoNTwkgWAxysp2w5Fp2iDI6FkoqVxYBz4sQJSYPM6q1sORaIoyRAOX78eCpE" + "W+VhzQIYsgAOwLC8TYvK4EQAB1DY66RBtIX1lIc1C4BQEgobHR1NhegAZXAsgDHDGgrj3CsN" + "ohMwjJYtxwIglITCjh49mgrRAc5YcGpqa2Xjxo2yZs0aWaiKR/m5aNonn0hLS4vodUEq1NTc" + "LO3t7UK7YbzBP3IgD3JZ9LXSzLJj505Zvny57Nu3T7p7eqS/v9/qzGzBggVO3h4tkwb19fU5" + "wyft2lxPGN6QC/mQszQR8OGqqqpKlv3wg7S2tloJ61UISjpy5IgcPnw4FaIDMITaguPlFzmR" + "F7lLGqRdu3bJ1q1bnTHcphf65UFJvb29cujQoVSoq6tLuHmNCw4yIC9yI39JAlRRUSH7q6tj" + "g5I9RlFw6M1tbW2pUEdHh3D7mg84hnfkRw8lBRC3iNu3b88bGGenruDQm5t1oo5KKDpqGSx0" + "cHAwEXDgHz2U1K0qEyMbx7hDmbtcXHDYSDa3tDsbyigAsVJLEhz0gD5KwnpYrcSd/IPmnKiW" + "MzZ2TOoaWuWPr051PvnfFqCkwUEm9FESq7jlK1YkYjHuOScKOAMDA9LRmZG/vj07S/zPcxuA" + "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" + "VvwtWIicAAAAAElFTkSuQmCC") + +#---------------------------------------------------------------------- +aero_dock_pane_bottom = PyEmbeddedImage( + "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") + +#---------------------------------------------------------------------- +aero_dock_pane_center = PyEmbeddedImage( + "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" + "AAAASUVORK5CYII=") + +#---------------------------------------------------------------------- +aero_dock_pane_left = PyEmbeddedImage( + "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") + +#---------------------------------------------------------------------- +aero_dock_pane_right = PyEmbeddedImage( + "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" + "SBhbDwAAAABJRU5ErkJggg==") + +#---------------------------------------------------------------------- +aero_dock_pane_top = PyEmbeddedImage( + "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") + +#---------------------------------------------------------------------- +aero_down = PyEmbeddedImage( + "iVBORw0KGgoAAAANSUhEUgAAAB8AAAAgCAYAAADqgqNBAAAALHRFWHRDcmVhdGlvbiBUaW1l" + "AG1lciAyNSBtYXIgMjAwOSAxNzo1MTo0MyArMDEwMMndnrAAAAAHdElNRQfZAxkQNALaVrQp" + "AAAACXBIWXMAAAsSAAALEgHS3X78AAAABGdBTUEAALGPC/xhBQAAAhJJREFUeNrtl01v2kAQ" + "ht81FgECjUQUyeKjBrfi0EOJktxzg0uOSaWe0lNP/Wk99lBy7A+oSiO1ORE+BChSTjGfBuPs" + "rATCNSkmyHtpX2ll2czOs+OdWTMMXIPBwKnVaqjX6xiORghK0UgEuVwOhmEgFosx1u/3ncrV" + "FYrFItKpFMLhcGBwy7LQ7nRQrVZRLpXAfl5fO4l4HLquBwb9U41GA2avB6V+e4t0Oi0NTMpk" + "MiCuOuavQlXVtRNGFs+NobPWLhphiO783YZ4gut3tc3OEN9/9fDQt5+0ebEbwttCHG9eR335" + "9A0v5LhDx0LlW+tJm6OTLAr59WDHcTaDiwXk96AqNj5/+eH57fzsEIa+t4k7KMur8TMMPYkP" + "744xMO8Wg+7puZ/5HrhfMI3ZbIb9ZByX708xHt6LK93Tc78+XHu+alXrlNdT+PTxgpepBtu2" + "fc8nu1AotB18Op1C0w7EdRMxxrwJtyn8uXOW9ezIt5ErctlwT+SUrTKkKIo7cvrUTSYTKXDK" + "dEo6V+RULrLkipxeuSy4p9QILmvP5yfhAi4z4VbCZZXaMkfZws/W+g//R+Hz4046fIe3R3S6" + "0YEftChIOtuJR1zRLvHiQ4r3afRxCbLeCRzhzWK73Rb/gNRXvGP8WqmILkLTtEAbRQqu0+3i" + "980NyuUyxGabpila5GarhVGALTJF/TKbFS1yIpFgj6VqglrJraorAAAAAElFTkSuQmCC") + +#---------------------------------------------------------------------- +aero_down_focus = PyEmbeddedImage( + "iVBORw0KGgoAAAANSUhEUgAAAB8AAAAgCAYAAADqgqNBAAAALHRFWHRDcmVhdGlvbiBUaW1l" + "AG1lciAyNSBtYXIgMjAwOSAxNzo1NDo1OSArMDEwMEcuCiQAAAAHdElNRQfZAxkQNxVyqGIt" + "AAAACXBIWXMAAAsSAAALEgHS3X78AAAABGdBTUEAALGPC/xhBQAABENJREFUeNrtV02IHEUU" + "/qq6529nFnezLtGNEEk0O5NZI4GoQaLxoCJ4NgjBgAe9K/5c9SToQbx6UaJgQLyJXgIRDUkk" + "t+iS1YDgwSWHMGOyO7Mz0z/l97qqZ3vGnd0ZYb1oDY9XXT39vve+eu9VtwJHo9H4+ufrK8+f" + "XQ6xuh5jt8ZCReNM3Ue9uvjl3NzcKUXgH06fvX7C1B7H08cNDpQUSvxjXlGofWqPWo8JYCiR" + "CCchdZe6Q71OudE2uPy9gv7tEj57qXpeXbh4yXzQfAzvnTQoGwWPKL4WMM4JrERgZdwhDgh/" + "sRExCMURLhjOu1rjrW8M3tz3I/QnPwW4/4RCPrIPjRpaWad2Er2Nl+JELoox+xwguP6tDYOD" + "RYW4sz1wnjzeaW3nnh2VKQXDvYpH/FUYWShpLBPXH4fGPCNaudHC+ctNNG6HI/+35y4fTx6b" + "wZGlaYTRznb91BszQmR0aKhWrUDo+fSrX0YaO/XsImqHp9HK+GcyOq2jOM6CG0kGByhabmo7" + "ly1kHqJNg/X6PF7xAnz48Xd/A37t1adwqDqP9cAM2jIZ+0NbkVSQgAUuI6VE0lLpZ2xs9Z2e" + "waHFBbz7xjNYa/7eF7mWdbkfG/QlGpqL/SDeZEDbyPlQbKx2YuhRMocTt367F+PevTN45+0X" + "0F5bTbRcy3r/WQzZyNplJIO0x7YMPIp2ks2XpM4zJXQr9FCvHcBH77+OBw7ux83OYJmmVKcM" + "JlE7SbekD57SnaUs21iGu5s8vLoBzNy3P9HDI7vnm83GYqg0w7MJF2QiV8YhO52oLep2Y0Q5" + "DQA6VgO332IvNNnIaSSIbB/3XaR9qmN3PUF/jV3kUZrA8WbCqeFSs3tiqEXY043tUHG6O2rr" + "yEcNM1QtiV1hwLW9NNtt5KH1Ktl7bR9Mki6lHtv37K0iz+5zWr6h29KByM81DPaS9z0Fhemc" + "QpnHWdGzbTWn7enmTQCenmI9onepW1xYC4Amy1Hx9wVtnUzBO4x8nTcKSicLXpKuPCA8m7GT" + "gKeUC3iX4NKaNwjeZka3erbWgyDajLwb2PY5xasC1xl8QnNKtSRiPCG4ZHaPtrpOpDIEQ4KJ" + "ogztkuotulrmYpEbnReqo836NhOCJ22UKD2JPpLorbRDY50bAA8t9V1Kj1T3tHt9cm8xySEz" + "IXjax/sMOIxkDIDDlQV2Pl7HAd9KUtvZMe574a6M/8H/q+D63/Mii+Pfzc+jMmugwka+2w4U" + "WdjlnMY054Lrv/xQDheuedj3RIQSi7/C7lLJASV2mIJnDxb/H/T2gC2xyN5e4PM5HpF5vqnM" + "c71M0LlvFc4c9eEfXTq8Ur96pfr58nHUHjV4cEpjhqAVgvHDgocNHYDteOOCy6nGMwT8KEGL" + "rP5JadCjX/mu98dFoBpfwbGHl2z3bDab5uq1ZZxbiXGztXufyPeUNV6sajxypI7Z2Vn1F7X+" + "m7ZM/KBNAAAAAElFTkSuQmCC") + +#---------------------------------------------------------------------- +aero_down_focus_single = aero_down_focus + +#---------------------------------------------------------------------- +aero_down_single = PyEmbeddedImage( + "iVBORw0KGgoAAAANSUhEUgAAAB8AAAAgCAYAAADqgqNBAAAALHRFWHRDcmVhdGlvbiBUaW1l" + "AG1lciAyNSBtYXIgMjAwOSAxNzo1MTo0MyArMDEwMMndnrAAAAAHdElNRQfZAxkQNALaVrQp" + "AAAACXBIWXMAAAsSAAALEgHS3X78AAAABGdBTUEAALGPC/xhBQAAAhJJREFUeNrtl01v2kAQ" + "ht81FgECjUQUyeKjBrfi0EOJktxzg0uOSaWe0lNP/Wk99lBy7A+oSiO1ORE+BChSTjGfBuPs" + "rATCNSkmyHtpX2ll2czOs+OdWTMMXIPBwKnVaqjX6xiORghK0UgEuVwOhmEgFosx1u/3ncrV" + "FYrFItKpFMLhcGBwy7LQ7nRQrVZRLpXAfl5fO4l4HLquBwb9U41GA2avB6V+e4t0Oi0NTMpk" + "MiCuOuavQlXVtRNGFs+NobPWLhphiO783YZ4gut3tc3OEN9/9fDQt5+0ebEbwttCHG9eR335" + "9A0v5LhDx0LlW+tJm6OTLAr59WDHcTaDiwXk96AqNj5/+eH57fzsEIa+t4k7KMur8TMMPYkP" + "744xMO8Wg+7puZ/5HrhfMI3ZbIb9ZByX708xHt6LK93Tc78+XHu+alXrlNdT+PTxgpepBtu2" + "fc8nu1AotB18Op1C0w7EdRMxxrwJtyn8uXOW9ezIt5ErctlwT+SUrTKkKIo7cvrUTSYTKXDK" + "dEo6V+RULrLkipxeuSy4p9QILmvP5yfhAi4z4VbCZZXaMkfZws/W+g//R+Hz4046fIe3R3S6" + "0YEftChIOtuJR1zRLvHiQ4r3afRxCbLeCRzhzWK73Rb/gNRXvGP8WqmILkLTtEAbRQqu0+3i" + "980NyuUyxGabpila5GarhVGALTJF/TKbFS1yIpFgj6VqglrJraorAAAAAElFTkSuQmCC") + +#---------------------------------------------------------------------- +aero_left = PyEmbeddedImage( + "iVBORw0KGgoAAAANSUhEUgAAACAAAAAfCAYAAACGVs+MAAAALHRFWHRDcmVhdGlvbiBUaW1l" + "AG1lciAyNSBtYXIgMjAwOSAxNzo0NDo0MCArMDEwMN+SkKkAAAAHdElNRQfZAxkQMBKjjWFJ" + "AAAACXBIWXMAAAsRAAALEQF/ZF+RAAAABGdBTUEAALGPC/xhBQAAAkJJREFUeNrtV02P0lAU" + "PS2lUEIlkxlHpCAEZ89Ol27M8A/czMK4duO/caeJiSZu3SgTN25M3DhBFkMmDhQJEK0apnwM" + "0wK1t+Z1YOqyr2w8yWuT95qcc9+97/UeAS6m06nTarWg6zrOZzPwhJJMolQqoVwuI5VKCcJk" + "MnFqh4eoVCrQcjnIssxVgGVZ6PX7qNfrqO7vQ/jSaDhqOo1isciV+Co6nQ5G4zFEvd2GpmmR" + "khPy+TyIW7xwt0SSpMgFECdxi5EzX8HGBDiOs1kBDH7ymaKoIAjCpQAiXy6XXAlFUfR4aBA5" + "EyBGET2R630LM+tvxpmQNQG8BpE3T8/w8cjEb3MREMe1BuLxOL4bY3z4NIAob/nzq1x+DYQt" + "IBaLodM18PpNA0p6Fwn5kpzqjXZmTUCYRUiR//x1hucv3yOl3lhbY4Gyd+g7QJGffG3jxau3" + "SCg7gXUWLH3nC6BfJI2wUC4V8OTxAZ4+qwXWiGexWPgp8J40EeYwTRM72xk8OrgfEMC+CRzD" + "sEUYhoFi4ToePriDuBTzBdD20zqrOWl1Mmz0ej1kMhncu3sTn5u2XwORHEOG4XAIbfcazm0F" + "aUXExdQO3oS8MXZbrz1t6ZL/gG3ba2uRtEKUYirMf2Hj/cB/AZ4A1hxsREDCdUKrVyNvULD0" + "HyBO4vackXsokXNtGR0R3t0RkSddf0iX1Hw+h3TbNYnvajXPKGSzWe7ekILsDwY4bjZRrVbh" + "JX80Gnnu+Fu3ixlnd0zR3yoUPHesqqrwB18A5ik1mQXQAAAAAElFTkSuQmCC") + +#---------------------------------------------------------------------- +aero_left_focus = PyEmbeddedImage( + "iVBORw0KGgoAAAANSUhEUgAAACAAAAAfCAYAAACGVs+MAAAALHRFWHRDcmVhdGlvbiBUaW1l" + "AG1lciAyNSBtYXIgMjAwOSAxNzo0NjozNCArMDEwMCXtbZ4AAAAHdElNRQfZAxkQLw561jOY" + "AAAACXBIWXMAAAsSAAALEgHS3X78AAAABGdBTUEAALGPC/xhBQAABAVJREFUeNrtV02IHEUY" + "fVXTO70/M+yOExNd4oIRkgljDIiyQZR4FD0rQlDw6E1RDwkoIqiHXLzlKK4Iwb2peFDJHgxx" + "dS+LcTMhhw0qJkvG7Jjsz8xOd1f5qqu6p9eNMB2Y9WLBm6+nf+p79X2vvqoSYFtdXf3ql8bl" + "52aWQlxbVxhkmyxJvFz3UK8dmq1Wqy8IOv/+xEzjSV17AsePaTw0KjDCF4cFUKQtEB6vJSz6" + "bWYYERFqoEvbpW3TXm1rfDsvIBsX8OlLte/E3PkL+vTqNN4/rlGCQEE6hzGEta5TkYOATsCf" + "SBtCGqGy9wJ2+vo5jbcqP0J+fDHA/qcEisqyHnSL6MTjz+GnBYxv+SdDso/x1rvgPBudvb6A" + "8Z0nrQNpXsJIZaxBNt/a/c8bJf0vME3pDAGlbG4ieikYLUgrPOV6MULU4i5EqK0j7XyYazMz" + "RGYkXsImhbAvpIJ0Q8/jHM55MmLl+out6hFMCUR8OyCksiMX0kYj7gV26CInCZVxbvofKQhs" + "0Jq6IPkkISKTFKQRcB9plWGtt+ujH8QRcBgfEmj+3sYe1hVTY0J3PyUQhYiLhHkQa8Hlalvo" + "7hITRYErl5r44txNrNyMUGQkEpKZFLBUKiu2AjKhduKLLXKKkB9MjQn89kcLs18vo+Dvi++b" + "gQZRTwtecjMk3UiaHAlb97VlGidfO2306dy8N+krNK5cx+kz51GamMKo77TB3Ea6Nw1lEoFA" + "WSKR00SaBpU/Dff4EteuN/HOB5/dUZyh01hKwITEpMDAEAl0EpV/6KIPDDPHlxYX8eprH94x" + "Oqb/bmT7TFMw09SodgJUWJ/HKZoSpWqmjU9BDMWrI1dJt0L20w7Wj+LMRyfxxtuf7Hi2fFvh" + "V+45vuTYp9IUBMAaZ8IGmW0SbaKjLLZULzr9ovFXhMn778V7p07sIGD62wyxvQ6ABDZC+6BD" + "bEUWXYdA5UOH33yzAhw++ADeffMZ+MVCSsD013YzIUMgIgHFkWt+TFBJW6TYpQ2IxOaB6efz" + "qwHWS+N4/tkDGC95lgCfbdJXkNUA1E7RKbcoZStknqacKBeaIer7q5jurGGC+8Em05Msfr0I" + "DLgttRRGDpQxe4Ob3s3tQ/F2g4BJ7Q90fivYuaP4z3dE/xOQye9uMjG+ksrg7RkRGKMwy0W5" + "KyR8yr7M+r5GPRrf3itHhjC3WODhJILPyVv27FowSgy7tWAo51pgmqkBoVtlhyPEffmePX+U" + "6bg8h/iM6D16pH6jvjC/9+zSMTw4rXGIZ8MJeivTMS/hmzOiMCTypSl2Trulbeldp71NIsu8" + "cfEnoHZrHo8dfdjuMVqtll74eQlnLyusbAz2gHbfmMSLNYnHH6mjUqmIvwGdqbciWIcx6wAA" + "AABJRU5ErkJggg==") + +#---------------------------------------------------------------------- +aero_left_focus_single = aero_left_focus + +#---------------------------------------------------------------------- +aero_left_single = PyEmbeddedImage( + "iVBORw0KGgoAAAANSUhEUgAAACAAAAAfCAYAAACGVs+MAAAALHRFWHRDcmVhdGlvbiBUaW1l" + "AG1lciAyNSBtYXIgMjAwOSAxNzo0NDo0MCArMDEwMN+SkKkAAAAHdElNRQfZAxkQMBKjjWFJ" + "AAAACXBIWXMAAAsRAAALEQF/ZF+RAAAABGdBTUEAALGPC/xhBQAAAkJJREFUeNrtV02P0lAU" + "PS2lUEIlkxlHpCAEZ89Ol27M8A/czMK4duO/caeJiSZu3SgTN25M3DhBFkMmDhQJEK0apnwM" + "0wK1t+Z1YOqyr2w8yWuT95qcc9+97/UeAS6m06nTarWg6zrOZzPwhJJMolQqoVwuI5VKCcJk" + "MnFqh4eoVCrQcjnIssxVgGVZ6PX7qNfrqO7vQ/jSaDhqOo1isciV+Co6nQ5G4zFEvd2GpmmR" + "khPy+TyIW7xwt0SSpMgFECdxi5EzX8HGBDiOs1kBDH7ymaKoIAjCpQAiXy6XXAlFUfR4aBA5" + "EyBGET2R630LM+tvxpmQNQG8BpE3T8/w8cjEb3MREMe1BuLxOL4bY3z4NIAob/nzq1x+DYQt" + "IBaLodM18PpNA0p6Fwn5kpzqjXZmTUCYRUiR//x1hucv3yOl3lhbY4Gyd+g7QJGffG3jxau3" + "SCg7gXUWLH3nC6BfJI2wUC4V8OTxAZ4+qwXWiGexWPgp8J40EeYwTRM72xk8OrgfEMC+CRzD" + "sEUYhoFi4ToePriDuBTzBdD20zqrOWl1Mmz0ej1kMhncu3sTn5u2XwORHEOG4XAIbfcazm0F" + "aUXExdQO3oS8MXZbrz1t6ZL/gG3ba2uRtEKUYirMf2Hj/cB/AZ4A1hxsREDCdUKrVyNvULD0" + "HyBO4vackXsokXNtGR0R3t0RkSddf0iX1Hw+h3TbNYnvajXPKGSzWe7ekILsDwY4bjZRrVbh" + "JX80Gnnu+Fu3ixlnd0zR3yoUPHesqqrwB18A5ik1mQXQAAAAAElFTkSuQmCC") + +#---------------------------------------------------------------------- +aero_right = PyEmbeddedImage( + "iVBORw0KGgoAAAANSUhEUgAAACAAAAAfCAYAAACGVs+MAAAALHRFWHRDcmVhdGlvbiBUaW1l" + "AG1lciAyNSBtYXIgMjAwOSAxNzo0NToxOCArMDEwMEtfu5QAAAAHdElNRQfZAxkQLyM/CW/t" + "AAAACXBIWXMAAAsSAAALEgHS3X78AAAABGdBTUEAALGPC/xhBQAAAkpJREFUeNrtVz2PEkEY" + "fnbZQ+AgGD+R5YSAxVkRLfwF5vgH11xlbeNPsbE2Ftf4C+RiY2OijeGu8GIusEjgiJ4Ej689" + "YHedd8zgsBgt3Fkan+TNZnY3eZ55v2ZeDQzj8dir1+uwLAsT24ZKxGMxFAoFFItFJBIJTRuN" + "Rl714ADlchlmNotoNKpUwHQ6RbvTQa1WQ2VnB9rh0ZGXSiaRz+eVEvvRbDYxGA6hW40GTNMM" + "lZyQy+VA3PoFc4lhGKELIE7i1kNn9mFtAjzPW68AgUXwhaKwoGnaLwFE7rru4gOZWKsiFwJ0" + "/+7tqQ6rw7JTVxcd4hOchvyCVPXOHbz9cA57soHtUhqO4yjxgMBSDogPo4mDN+/OcCVt4Ob1" + "JGazmTJvLEIgu4Uwmzt48fI9mq2vPBzyP/9qlF9LZeh/KeP5/mucffuOSCTC/wnCBOcfPSDj" + "6bN9fDppBOYJucJ4DtARSfbzKI6vCHjyeA+X05sYstMrCJA3RZVxAZTpwvx4tPcQ166m0ev1" + "Ak2+lTIkctk1G0YEu7sPeBV0u91AyQmCyxALEiBUbcYjuH/vFnsC7XY7cHK5yS01Iqr3ZELH" + "3VIc5g0X/X4/cHKx4d+ehiTgYvwFd0w3sIT7G1auQiRCZefzY+33gf8CuAD5eAxdwCXWfqkH" + "qLyAyKDNUismTuLmkxErSmTZWEbZr/puSOQxNh9Sg5vP5zBKbEh8Va3yQSGTySifDWmTndNT" + "fDw+RqVSAQ/+YDDg0/HnVgu24umYdn97a4tPx6lUSvsBjEDOU65zEi4AAAAASUVORK5CYII=") + +#---------------------------------------------------------------------- +aero_right_focus = PyEmbeddedImage( + "iVBORw0KGgoAAAANSUhEUgAAACAAAAAfCAYAAACGVs+MAAAALHRFWHRDcmVhdGlvbiBUaW1l" + "AG1lciAyNSBtYXIgMjAwOSAxNzo0ODo0NiArMDEwMKZ+RR0AAAAHdElNRQfZAxkQMQU5RdXP" + "AAAACXBIWXMAAAsSAAALEgHS3X78AAAABGdBTUEAALGPC/xhBQAAA/ZJREFUeNrtV09oVEcY" + "/83sy27UfZhtNLWxKNaDK6sNlJZIabHgpdB7KYiCR28W7EEPIkLbgz1481hIaSt4a6UUWkyh" + "Imm9hKbpSg7pIUVjotnGJG5235uZft+bmc1bk8A+NTn1W743b98bvt9vvn9vRoBkbm7uxp/V" + "ux8Mjce4t6ixkdJflDhZCVApH7je29v7oSDwX48PVd8x5bdx9IjB/q0CW2hitwDyNOZIA7qX" + "sNqp8DIUaWyAJo1NGus0/l03+GlEQFZv46sT5Z/F8K3b5vLcID49alCEQE46wESFHZ1RkYGA" + "8UoXZZiQQazts4iMfnzT4JPSb5BfjkV49V2BvLasN1oUgQR0OfieAGPLh+SSl8nfZhPA097p" + "KwgwdpawbogEnpFOjazpeBv3P6uXzDrKok2KgNY2NopQcpwL0iaedlY4EY14hiQ0Fsg4DL7n" + "yhCplQSeTUuFndBKSLf0LOBw4H7F2tlLRr1CsEVA0eyIVNLLAi19K9VhnWvHOOc7UllI6BQ4" + "21euHLkvSHrjiUgfAp7I9d9H/n44VUdPl1hhbdrzoxM1ZrV6W7FfmyegYiRNIp8TmH6k8N3N" + "R5j4axalvGwj8TyqUupJrhDgdqktCZb5xRjXf5jEvw9q2EutudXNMgKyPT96jVgV2kNgX1Jc" + "Ur2w0VS4+MWPqE5Mob+giAS9z6j84/jrVC7ERrcW0+aByJXi03Lhs69x7/4sXqLszOIBg7Xd" + "7xPRpD3ALklCsE6nOX3mc1RHR9FNOaLWMLiWxs5e7O4jF+amsuoXm5Th0KxB73JEEwMMyNXF" + "dvXKOQQ7dmFiXnVchp5IRC5oENgy/VmiZJ+n7zKtA9/T2ve0+kAELNDLZY1VH/1L54+j/5Wd" + "+GVGZWrF3tVRAm71CWEsKQuq0x4AEWB2DdV6gkI+h7Nn38ee3SXcmIptvDJ0Ik8gcbu2thMi" + "ZIr3HJFqI6CIgKaJtvNtLwY4duw1LBS349vJJgJC9puULASiBNwkMW/QAw4Dd1jpynGFgKvR" + "Wfo+9+yUGBwIgd0hRmbipDXL9LehQ1mvCTEOL0alq8DLP080vpmOkd8XYqzWecI9j6zakEzX" + "NW49iFGPs6TcCySw2fI/Aemvm8mEsXLuPtixRWAblURI3/7NIFGgwg+7JBYoxxk7OHW4C8Oj" + "OTqcKBSoaEMq0iIpb8u6iSbNRRedkHLP0Ih8K+6mimZbhcCeP0ICDoeRnBGDNw5XZip3Rvqu" + "jR/BvkGDA7QB6SG0kPeGBFjgM6JgEtnClIDT2OAzIZFYpPExEZmkB2O/A+X5Ebw5cMjuM2u1" + "mrnzxziu3dWYXtrYA9qubRIflSXeer2CUqkk/gNN/sDRnOMoBAAAAABJRU5ErkJggg==") + +#---------------------------------------------------------------------- +aero_right_focus_single = aero_right_focus + +#---------------------------------------------------------------------- +aero_right_single = PyEmbeddedImage( + "iVBORw0KGgoAAAANSUhEUgAAACAAAAAfCAYAAACGVs+MAAAALHRFWHRDcmVhdGlvbiBUaW1l" + "AG1lciAyNSBtYXIgMjAwOSAxNzo0NToxOCArMDEwMEtfu5QAAAAHdElNRQfZAxkQLyM/CW/t" + "AAAACXBIWXMAAAsSAAALEgHS3X78AAAABGdBTUEAALGPC/xhBQAAAkpJREFUeNrtVz2PEkEY" + "fnbZQ+AgGD+R5YSAxVkRLfwF5vgH11xlbeNPsbE2Ftf4C+RiY2OijeGu8GIusEjgiJ4Ej689" + "YHedd8zgsBgt3Fkan+TNZnY3eZ55v2ZeDQzj8dir1+uwLAsT24ZKxGMxFAoFFItFJBIJTRuN" + "Rl714ADlchlmNotoNKpUwHQ6RbvTQa1WQ2VnB9rh0ZGXSiaRz+eVEvvRbDYxGA6hW40GTNMM" + "lZyQy+VA3PoFc4lhGKELIE7i1kNn9mFtAjzPW68AgUXwhaKwoGnaLwFE7rru4gOZWKsiFwJ0" + "/+7tqQ6rw7JTVxcd4hOchvyCVPXOHbz9cA57soHtUhqO4yjxgMBSDogPo4mDN+/OcCVt4Ob1" + "JGazmTJvLEIgu4Uwmzt48fI9mq2vPBzyP/9qlF9LZeh/KeP5/mucffuOSCTC/wnCBOcfPSDj" + "6bN9fDppBOYJucJ4DtARSfbzKI6vCHjyeA+X05sYstMrCJA3RZVxAZTpwvx4tPcQ166m0ev1" + "Ak2+lTIkctk1G0YEu7sPeBV0u91AyQmCyxALEiBUbcYjuH/vFnsC7XY7cHK5yS01Iqr3ZELH" + "3VIc5g0X/X4/cHKx4d+ehiTgYvwFd0w3sIT7G1auQiRCZefzY+33gf8CuAD5eAxdwCXWfqkH" + "qLyAyKDNUismTuLmkxErSmTZWEbZr/puSOQxNh9Sg5vP5zBKbEh8Va3yQSGTySifDWmTndNT" + "fDw+RqVSAQ/+YDDg0/HnVgu24umYdn97a4tPx6lUSvsBjEDOU65zEi4AAAAASUVORK5CYII=") + +#---------------------------------------------------------------------- +aero_tab = PyEmbeddedImage( + "iVBORw0KGgoAAAANSUhEUgAAACAAAAAfCAMAAACxiD++AAAAAXNSR0IArs4c6QAAAARnQU1B" + "AACxjwv8YQUAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAA" + "AwBQTFRFq6ysra6urq+vr7CwsLGxsbKysrOzs7S0tLS0tLW1tba2tre3t7i4uLm5uru7vL29" + "v8DAwMDAwsPDxMXFxsbGycrKzc3Nzc7Ozs/P09TU19fX2dnZ2tra29vb3Nzc3N3d3t7e4ODg" + "4uLi4+Pj4+Tk5OTk5OXl5ubm5+fn6Ojo6enp6+vr7Ozs7e3t7e7u7+/v8fHx8vLy8/Pz9PT0" + "9fX19vb29vf3+Pj4+fn5+vr6+/v7/Pz8/f39/v7+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAA0PbvAwAAABh0RVh0U29mdHdhcmUAUGFpbnQuTkVUIHYzLjM2" + "qefiJQAAAP9JREFUOE+t0tlSwjAUgOFjZQuxuOBSFURSSOyq0qYNEd//tUzCaIeLk9743Zyb" + "f5LOSUH3AC3vyEWIuIw06KvHRmGqKIJ3+u1RzaD0BpL+S3DwfIO5oqAHDxPk1LOpr9oGe0/h" + "ArXHmSCjLbpIpSobyBa3o5DSWqJaF+xqlPykEE/LHFWmU2AkS1GZCdYkFajkjZhAxCghCLwS" + "zlCcE1j1BpOeE176gqU/mMBy3F0Rb/mpZENh0QWxyJNTfD6HRXfFthgNndFgcJzDa2aCv0WZ" + "tUj7blLdP9nZ6PCZATtPft+qjELt/vAm/HCzCNYmuA2O5xqzmzOwAuIG0AfGfgDFvqY+8bKe" + "lgAAAABJRU5ErkJggg==") + +#---------------------------------------------------------------------- +aero_tab_focus = PyEmbeddedImage( + "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") + +#---------------------------------------------------------------------- +aero_tab_focus_single = aero_tab_focus + +#---------------------------------------------------------------------- +aero_tab_single = PyEmbeddedImage( + "iVBORw0KGgoAAAANSUhEUgAAACAAAAAfCAMAAACxiD++AAAAAXNSR0IArs4c6QAAAARnQU1B" + "AACxjwv8YQUAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAA" + "AwBQTFRFq6ysra6urq+vr7CwsLGxsbKysrOzs7S0tLS0tLW1tba2tre3t7i4uLm5uru7vL29" + "v8DAwMDAwsPDxMXFxsbGycrKzc3Nzc7Ozs/P09TU19fX2dnZ2tra29vb3Nzc3N3d3t7e4ODg" + "4uLi4+Pj4+Tk5OTk5OXl5ubm5+fn6Ojo6enp6+vr7Ozs7e3t7e7u7+/v8fHx8vLy8/Pz9PT0" + "9fX19vb29vf3+Pj4+fn5+vr6+/v7/Pz8/f39/v7+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAA0PbvAwAAABh0RVh0U29mdHdhcmUAUGFpbnQuTkVUIHYzLjM2" + "qefiJQAAAP9JREFUOE+t0tlSwjAUgOFjZQuxuOBSFURSSOyq0qYNEd//tUzCaIeLk9743Zyb" + "f5LOSUH3AC3vyEWIuIw06KvHRmGqKIJ3+u1RzaD0BpL+S3DwfIO5oqAHDxPk1LOpr9oGe0/h" + "ArXHmSCjLbpIpSobyBa3o5DSWqJaF+xqlPykEE/LHFWmU2AkS1GZCdYkFajkjZhAxCghCLwS" + "zlCcE1j1BpOeE176gqU/mMBy3F0Rb/mpZENh0QWxyJNTfD6HRXfFthgNndFgcJzDa2aCv0WZ" + "tUj7blLdP9nZ6PCZATtPft+qjELt/vAm/HCzCNYmuA2O5xqzmzOwAuIG0AfGfgDFvqY+8bKe" + "lgAAAABJRU5ErkJggg==") + +#---------------------------------------------------------------------- +aero_up = PyEmbeddedImage( + "iVBORw0KGgoAAAANSUhEUgAAAB8AAAAgCAYAAADqgqNBAAAALHRFWHRDcmVhdGlvbiBUaW1l" + "AG1lciAyNSBtYXIgMjAwOSAxNzo1MToxNCArMDEwMESarloAAAAHdElNRQfZAxkQMyBAd2MK" + "AAAACXBIWXMAAAsSAAALEgHS3X78AAAABGdBTUEAALGPC/xhBQAAAh9JREFUeNrFl0tv2kAU" + "hY+NBXbAElS0QTwColKX4Q+gbvmDXXXVriq160olq7b7Rk2zICsejWyUqkmQgEIMxvWxZAti" + "0eCoHo40ssZj+5vrO3fmXgmuut2u4zb0+33MZjPEJU3TUKvVUK/X2SRlOp067ZMTNBoNNJtN" + "JJPJ2OCWZcEwTXw/OwO50o/zc0fPZFCtVmOD3tdgMMB4MoHc7/VQKpWEgalyuQxy5Tv3VyiK" + "IhROHrmyUOo97QXuOM7+4L4CZ/uziVuSJG3CCRYFX5+AcDg5iURiP3Ba7bOE+3xde/H5huWi" + "4SHLV6uVEKAsy5uW86hbLBZC4FzpoVCzbVsI3OcFcP5yUfBQqBEuyufkhFb7Y+BMubheHguX" + "fXiURjH7sSw7yIKivh/Ao4g+KxQK6A1MvHr9wbuyv35a7arI8Gw2i+ubCd6++4KUlveu7PN+" + "rPBcLoer33d48/4bDvTDoLHP+xyPop0zR13XYfxaov3VhJZ5Fhr/+NlE62UFh090jMfj/wdP" + "p9O4uk3itDNB6iC/9bnTjoXjFxmUnu42AQ/+0GKZz+c4KuaQz6kPflBTJYxuRrtZnnJjlbsb" + "N/xtsc7x2+thkIH8S6M/9tbdkkbyGxwn1yuX3OBDsVj0Dpc4j1aCVVWFYRhYLpdQnrsV46d2" + "26siGK9xFoo0zhwO0bm4QKvVgudsd3F4JfLPy0vPv3GJVh9VKl6J7EaP9Be4+2JJRD7+lAAA" + "AABJRU5ErkJggg==") + +#---------------------------------------------------------------------- +aero_up_focus = PyEmbeddedImage( + "iVBORw0KGgoAAAANSUhEUgAAAB8AAAAgCAYAAADqgqNBAAAALHRFWHRDcmVhdGlvbiBUaW1l" + "AG1lciAyNSBtYXIgMjAwOSAxNzo1NTo1MyArMDEwMAycPlQAAAAHdElNRQfZAxkQOBMcU9vX" + "AAAACXBIWXMAAAsSAAALEgHS3X78AAAABGdBTUEAALGPC/xhBQAABFhJREFUeNq1V82LHEUU" + "/1VNz863yWyUyCSaEPzYuGtkwY8QYwLiQfTgwYsgBPMXeDJXvYmo+Qc8KEFIULyJHhQWNcSE" + "3KLrLgqLoMYVkplIdmZ2PrrL36uqnulOsu50YB68ed3V3fV771fvvZpSoDSbzS9/Xll9+czy" + "EFc3IkxLGlWNE/MBjj17RBljPlQE/uH1MytHzcEjeOGwwYGSQokvzigqbUCbo9UTAhhqKMqL" + "IW2PdpN2g/pbx+DH7xX02gWcPblwWi2dv2Debz2Dd48bVIxCjiiBFjBeE1iJwumkIg4If5ER" + "NRiKIxxgtOhpjVNfGby15xL0xz8NsP+owkzoPpqmiBP5MEL9RUBw9bWuwe6iwvRWOi2C0yhp" + "CO6kSzkVCWJvzBYqopBtScwdruM8sHj+wkYuiWGMBxTrHxrvQdZcMCat0S33qcgFbMBBTau0" + "y3CL6K0yfmxCibzDoYkz3pdelGbA0U53Ir4RqfHLmq9FcHVmSy1D+GbEphnNF5cdLFYuAR65" + "MsgJqNcwMZkFzxB5THXcbGzUXk1iGS240BGmPEw3lqwlkcyfcbNxGHbOKEW7W/M4cpVYb7Em" + "I+0pQM/qIHIq8w1NMnLyMwhdHw98pCOqI39/NwnnGY2tMKwwLjUL7tbE0Iqyp1Ot5/HqqOwJ" + "FyYot/MKA5EZMTOOfOi8smuv3Yc26RLdRWeMPLnOoU++oV/SVOTnmuzv5H22oFDLK1S4nRVZ" + "DTN0JK/d7pbLAB7vYn2i92jbHLg5AFp9KV6Fs5zreAy+ycg3+KDADiMDOZuuCibnMjYLeEy5" + "gPcIvsmQuwTvMKPbfWNrfTAIx5H36FWHDpR5V+A4g7c0x1RLIkYZwSWz+5yr57UbOgwJJgwT" + "tEuqt+lqhYNFLvSMUB2O69tkBB/adm1IuwBL9E47ZMA6lwIfOup71D6p7mv/98m3VvHC3AIu" + "tzuYI//2zG27WJy8cW1bBjyGFQ8+al5RQs02Ksvxyr4Ae9t/WqvV9t8k548l858JYeOlhsLy" + "yhrePHXaWrnPUg13DX5oNsDf/9zAO+99jnKtYa3cy/hUwRd3Bbj5xzre/uAb1Or7Rir3Mi7P" + "s8jEbz+yI4fm79fxyRdrqO588LbnH322hjdezePRvbP4pRVONOdE4PtrGr2rHXx7qWup3krk" + "+bGwjYceqNCB4bbzOnD9//yv86Tx/IEKGrvL205YLSssXds68iROcC+PRxXmf5WNfCsHpEF8" + "xwlLE/DUve7a6Z2kyO8reY0arwU3OPl4HktXctjzHCenw1V2l2oeKLF2Cjm3scQNZzjByUKO" + "WvfwoDdgSyyytxf4fZ5b5Az/qdxHnyoE3fW1wonFAMHiwmOr85cvzn26fBgHnzZ4uKyxk6BV" + "gvFgwc2GDsB1vEnEdjj+cA8BDyVo0+Eb1CbZ+5Unxr/OA3PRRTz5xILrnq1Wy1y+soxzqxHW" + "29M7ON1f0XhtTuOpQ/Oo1+vqPxxtdiUOpmR7AAAAAElFTkSuQmCC") + +#---------------------------------------------------------------------- +aero_up_focus_single = aero_up_focus + +#---------------------------------------------------------------------- +aero_up_single = PyEmbeddedImage( + "iVBORw0KGgoAAAANSUhEUgAAAB8AAAAgCAYAAADqgqNBAAAALHRFWHRDcmVhdGlvbiBUaW1l" + "AG1lciAyNSBtYXIgMjAwOSAxNzo1MToxNCArMDEwMESarloAAAAHdElNRQfZAxkQMyBAd2MK" + "AAAACXBIWXMAAAsSAAALEgHS3X78AAAABGdBTUEAALGPC/xhBQAAAh9JREFUeNrFl0tv2kAU" + "hY+NBXbAElS0QTwColKX4Q+gbvmDXXXVriq160olq7b7Rk2zICsejWyUqkmQgEIMxvWxZAti" + "0eCoHo40ssZj+5vrO3fmXgmuut2u4zb0+33MZjPEJU3TUKvVUK/X2SRlOp067ZMTNBoNNJtN" + "JJPJ2OCWZcEwTXw/OwO50o/zc0fPZFCtVmOD3tdgMMB4MoHc7/VQKpWEgalyuQxy5Tv3VyiK" + "IhROHrmyUOo97QXuOM7+4L4CZ/uziVuSJG3CCRYFX5+AcDg5iURiP3Ba7bOE+3xde/H5huWi" + "4SHLV6uVEKAsy5uW86hbLBZC4FzpoVCzbVsI3OcFcP5yUfBQqBEuyufkhFb7Y+BMubheHguX" + "fXiURjH7sSw7yIKivh/Ao4g+KxQK6A1MvHr9wbuyv35a7arI8Gw2i+ubCd6++4KUlveu7PN+" + "rPBcLoer33d48/4bDvTDoLHP+xyPop0zR13XYfxaov3VhJZ5Fhr/+NlE62UFh090jMfj/wdP" + "p9O4uk3itDNB6iC/9bnTjoXjFxmUnu42AQ/+0GKZz+c4KuaQz6kPflBTJYxuRrtZnnJjlbsb" + "N/xtsc7x2+thkIH8S6M/9tbdkkbyGxwn1yuX3OBDsVj0Dpc4j1aCVVWFYRhYLpdQnrsV46d2" + "26siGK9xFoo0zhwO0bm4QKvVgudsd3F4JfLPy0vPv3GJVh9VKl6J7EaP9Be4+2JJRD7+lAAA" + "AABJRU5ErkJggg==") + +#---------------------------------------------------------------------- +aero_denied = PyEmbeddedImage( + "iVBORw0KGgoAAAANSUhEUgAAABYAAAAWCAYAAADEtGw7AAADxklEQVQ4jbWUzWuUVxTGn3Pe" + "+955k8nMJE5m0vqRZCaUYomG0uJGjDR0UXBRUj+6Kqg7kSy6SBf5A7ropiB07aK4qIsWIYsu" + "ioXBQoRKqWhJ1eq0sWqSmWQyk8y8H/e+p4t8oJ2YuPHA2dzD+fHwnHMuiQheR/BroQJQOxWv" + "Z7N9cRB8nAA+dIEBAIiAvwPgJ04kro1Vq/Mv66XtrCgVi9osLEx1K/V57+HD6a5Dh8B9fSDH" + "ga1WsXr7Niq3btVrUfS1yue/HH34MNwV/HM+n1HN5vd7+/vHsmfPQu3bt60i8+wZli5fxr/l" + "8nXT2fnJBwsLK8/XX/B4ZnhYqWbzyv5icaz34kXwnj2IrYUoBXge4HkQpRBbC85kkL1wAfuH" + "hsZUs3llZnhYvRQclMvnsqnUidTp04DWoEwGnE6DEwmQ44AcB5xIgNNpUCYDaI3UqVPIplIn" + "gnL53LbgmULB9YCp9LFjcHM5KM8D+z6o0QD+l9RogH0fyvPg5nLIHD8OD5iaKRTcTd6W/KhS" + "OZJOJgc7R0ag4ngd8gqhAHSOjMArlQbrlcoRAL+8oJhFjiaLRWhmrN29ixYzfNdF6LqIXBdG" + "a1itEWsNaA24LoQZ5s4duAC6hobAIkfbFDOQ78jlQPU6zNIS3jh/Hqz1jmolDFE7eRIqk4GX" + "zYKBfDtYJGDfB9VqkNXVV7IBAOT+fYjrrs9DJGgDE/Ns+OABkExCBQHmL10CM4OMAVkLAkAb" + "3hEAIgJZC7YW9OgRwnodzDy7xds8kF97et70iMrFfF6T1gAzEEWAMburBvBwcTH0RQbfX15+" + "Cjw3vIOl0tMgiq7VVlbA9Tq4VgOvrYGDYNes1esIoujawVLpaZtiAPitu/sta8yt/o6OVI/a" + "8X/aimVj8E+r1XCUeu/dWu3+tmAA+D2dHvetvdqtlDrgOEgQbQsMRDBnLWrGGM9xzozU6z88" + "X28D29VV/LF370d+HH/bFOntIkKGCJuLFwJYEcGqCDqJKh7zZ+88efKj09WFHcFbtuTzOW61" + "vghEPo1EDtiNdweASzSnib6Tjo6vum/eXCwUCm39dO/ePUxMTDizs7Oq0WjoMAy1tVbHceyK" + "iJsFOs4wv32AqF8AzMXx3FWRP5cAn4hCZo6YOdRah8lkMhwYGDCTk5NGzc/PY21tDcYYiuOY" + "RIQ30gGgKiL4xpi/AJSxvsIWAIjIAaBEJBYRx1rLxhhqtVqoVqu0rRXT09N048YNPH78mKrV" + "KjWbTfi+TwDgeZ4kEgn09vZKX1+fjI6OYnx8vA3yHxWIwp50Lj49AAAAAElFTkSuQmCC") + +#---------------------------------------------------------------------- +auinotebook_preview = PyEmbeddedImage( + "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0" + "RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAJXSURBVHjahFJRSBNxGP/d/+52" + "W66ildPKJB9EMSwjFSNoUkS+GYg9pi9R9tJeopcgC3yKYvRUMHA+VBJUmpQPQSySKINqEGSB" + "Np3aNjs3N8+7285d/7sxa9yw3/G777v/932/+76PP6PrOgw8D876dYImWJAEZPpW8l89nYea" + "i8KGgMHRYPitvgkCw0G9qaNXD4x80Qs1BknRnzaBEfX1e+G758PQaEgvnP8VULA5lCS8/T7T" + "NUQK4ApO4j/1l3s6N/zA8MiGTxiGgUGowGwkhqkfc5Bl1SKwlM6i90y75dzsoKX9fNPLF2Mt" + "sZVGuMt3YPDpK7jsDnj7uiEIfH6ClAjF5rAKHD1xcW99a927C5e6hP3luwCGwONpRjwm4tqA" + "H7du9pmJDG9HrsSeSE3dvmdnz512tFZVQaGjaCyBjedRubscxzva8PrNJzNR0xmsKsQq4KzY" + "fqDW7UY8o4KjxRzLmpYlBLUNNZj48BVRMQlWA7I5yTqCvcyuq+s0qmkQiC1/QeiTo1ajNpWS" + "oMoMVJVyXbZ2sDj9S5sMh2l7CjJUJGswq0HNZBCNLmEhPA0xsQpJUSHJJUZILCbuxuZF7fv8" + "ApbTaaysrSEpSVgURdy//RDJxAp2RvzgMgkQzbpEziVEbgQfBeuXPY1dqcMS4W08cnIWY0Pj" + "iIXjuNI2A0dsDmXyT3yUTtESZ5EAU3CuDvhD7ydDB1mWg6zQWelSj2ydwbE9v1Fd4TQZmHLj" + "yTfBzP88PsgUCZTCne4qFycID7Zt4TuqK50TFaTmZMP1x5l/c/4IMABbKBvEcRELXgAAAABJ" + "RU5ErkJggg==") + +#---------------------------------------------------------------------- +whidbey_down = PyEmbeddedImage( + "iVBORw0KGgoAAAANSUhEUgAAACsAAAAeCAMAAACCNBfsAAAAAXNSR0IArs4c6QAAAARnQU1B" + "AACxjwv8YQUAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAA" + "AwBQTFRFAAAAaHWVZ3egZ3e1cYCncYa0dpC4cYXGcpDBc5fWd6Dld6Pwi5OzgJbUkJjQkqHC" + "hafggqfwh7Dwk6Xgl7fwoKjWp7fXsbbWoLfnoLjwpMLwscHnsMfxtdD0wMHg5eTo5/D49/f3" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAverH2wAAAQB0Uk5T////////////////////////////////" + "////////////////////////////////////////////////////////////////////////" + "////////////////////////////////////////////////////////////////////////" + "////////////////////////////////////////////////////////////////////////" + "////////////////////////////////////////////////////////////////////////" + "////////////////////AFP3ByUAAAAYdEVYdFNvZnR3YXJlAFBhaW50Lk5FVCB2My4zNqnn" + "4iUAAAF4SURBVDhPbZTpYoIwEITRGqAQ1ChqOFrx/R8y3SObq8yvsHwZhglaOdH7HZbJIp1W" + "YW7t7w68LOcwDaxd5+lhC3q18zwOMhTWIjtZ+3wGfFmWFYZjgJl9vWC/1x30AI2zzExzIcqz" + "a2RxC2Ak2n4zJmEx1TgaY8R7AtHsdgMS1IivJRZHyN5lA6PMEgwZ2JVYs23bxwuWG3PGdF3X" + "ImvtBO/KqAl38ytgO+WqMz1SDDz7KXaCbatU5YYARt+SbUHg61zT9/iMHsQZKHBI9A1S6njk" + "fpuCRTjmJVQBymfxlfuCc5IX0cMhnhvCMYO8KCdCV0L9GbMz8Fhqous1ugbWw/32k2oDtFUn" + "do2sz1ywqWvCElz6pq4pS3CRVxooMvjqsI2+v5LwXCVrljfrGclhqH2v5e+Nr6VnYltgBct7" + "iDDExm+grvm0ouL/QwrjN1CHXvczYAz0xF5Pp9w168zfAhjeH9gSle8hnWutldL6H7rHOq2P" + "agd1f/M7VhKuYPh3AAAAAElFTkSuQmCC") + +#---------------------------------------------------------------------- +whidbey_down_single = whidbey_down + +#---------------------------------------------------------------------- +whidbey_down_focus = PyEmbeddedImage( + "iVBORw0KGgoAAAANSUhEUgAAACsAAAAeCAMAAACCNBfsAAAAAXNSR0IArs4c6QAAAARnQU1B" + "AACxjwv8YQUAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAA" + "AwBQTFRFAAAAECBXFC9vECiQBS2oIDKMIDigC0CVJUqQIkewJFKlJlayMEiwMlSiK2G0JFTR" + "LmnSKGHmKHDoInDwMGjgNnDgNXLwSFeKQFizR2OkUGagcHiYZ3egcHilUHjRQXLwR4DwU4Pw" + "YIfTcYXGcIjVc5fWZpHxcJj3d6PwgJbUkJjAkJjQkqHChafggqfwh7Dgh7Dwl7fwoKjAqLDI" + "sLDIl8D4pMLw4Of35/D4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAQS37pgAAAQB0Uk5T////////////////////////////////" + "////////////////////////////////////////////////////////////////////////" + "////////////////////////////////////////////////////////////////////////" + "////////////////////////////////////////////////////////////////////////" + "////////////////////////////////////////////////////////////////////////" + "////////////////////AFP3ByUAAAAYdEVYdFNvZnR3YXJlAFBhaW50Lk5FVCB2My4zNqnn" + "4iUAAAGeSURBVDhPfdOLUsIwEAVQCCixFSggVfBFQWpVjKJo+f8fi3ezSZq2jDsDhHB62Wyh" + "o319Vctg9V2tO34p5H0bHw5D4Xe9FULK5bKhb6UcDs/cprNdMYiprhee30khaStx2NpudwCb" + "JHG8WAhbEkU0cZgtUge0iYrjCEUuilheOWysS2U9GpluGJJ1mKyg1LHZn6NYNNYUCWuozSjr" + "FVxnLGQUjZPJlLJKCk7piar0Nk2zS90RAhINVDZN5yUeNZuhbjq6BzmZomYoICr/Yr5mtcry" + "fL2mfnvjSd2anvkSsqBZUazN2XS/YVNLrc2Qut3yHAj/10OWFwVRtrpPvZo5cL+28A7fnxfv" + "G6Ps74GwsfXCsXKkMnWWko39DKukAbjUygKfskFqYLnnKvcHdQT9sA0EPdgD1ts9Vr2G/fpp" + "pDMeAg3gWe2fgn9V9d+0yW5edCylQurnwJejZ/7VPJpZ7V+C1PBsFrPFXAul6rSRi2S2uFmq" + "kdrK1frcWOq1kXrC6osHulnq963Wa3Nm9kOySrVpq1/yr5vNbtdK1foPGIxy6qmqIg0AAAAA" + "SUVORK5CYII=") + +#---------------------------------------------------------------------- +whidbey_down_focus_single = whidbey_down_focus + +#---------------------------------------------------------------------- +whidbey_dock_pane = PyEmbeddedImage( + "iVBORw0KGgoAAAANSUhEUgAAAF0AAABdCAMAAADwr5rxAAAAAXNSR0IArs4c6QAAAARnQU1B" + "AACxjwv8YQUAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAA" + "AwBQTFRFAAAAaHWVcHiYZ3egZ3e1cHilcYCncYa0dpC4ZobHcYXGcpDBc5fWcZjgd6Dld6Pw" + "i5Ozl6e3q62xgIjQgpXEgJbUkJjAkJjQgJjggKDXkqHCk6TWhafggqfwh7Dwk6Xgk7Tkl7fw" + "oKjAoKjWqLDIp7fXsLDIsbbWoLfnoLjwsLjjl8D4pMLwscHnsMfxtdD0wMHg6tTN4Njk5eTo" + "4Of35/D48Ofh9/f3AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAA1jVbdgAAAQB0Uk5T////////////////////////////////" + "////////////////////////////////////////////////////////////////////////" + "////////////////////////////////////////////////////////////////////////" + "////////////////////////////////////////////////////////////////////////" + "////////////////////////////////////////////////////////////////////////" + "////////////////////AFP3ByUAAAAYdEVYdFNvZnR3YXJlAFBhaW50Lk5FVCB2My4zNqnn" + "4iUAAAdFSURBVGhDtZpxe6I4EMbtbQVXrHJuaY8KWq/XW3Sr196u8v0/GTfzThICRIh9nssf" + "u4rw42UyM5kJHVVXjDQah/EV51ejK05+SKMwjK/BX0Gfzu9ms2kY3PgL8qd/nc/vCB+GN/54" + "b/p0sVjMaRA+9Mb70hku9GvUe9IBV3TC+9rejy5wTfdX70VneJ7LHRaLu7uIPNPL9j50hp+a" + "w9NzPOhQfvrHHqeZn/phOmzeofvZfpA+zTFa2rNsFk2GPWeIruB5y+5En00mg1E7QNfw/CSP" + "oEaZ51kWDavvpxu4phOWB+hJMqi+l17DFb0sBa+eJB3KOX30xLIFeAQXvKYTvtf2PfTVbrtd" + "6xsonrKMoafhJAwu5/vL9NWO6Rp/iZ5OJsFlfI/2onh7oxvQYKOsaeALjTV944fK2DFn489o" + "ryqmA69NDrtjWPSwZyHs9RlWr+h5Qzt9UdqzPvhATTBMn/Uu4QOxut9r24vFdzSUvUh+lk37" + "6wMXfVNf8sF0mdrt9nGF8YbB3ppNl/WpK8eNHPTNxuCXHx/a9q+vZz3exZvW6+m5NPhi58B3" + "6Yfj2zYRHcuSgOI5Bi0fDgeiT/nT8oxTi2K3U1dZz9ChH5gu+GXJ9HNRvP/dgoOfyEGox/Tn" + "nVlo04kNm34DHPRfv7psxuujywqm4rBo41t0Vs4uvl4/PXHUNLj7/f6VRvte5RJTjxBo4Zv0" + "A3sDxw2d+EeTcnzd4qnIdR43zZ/EbSUjNfEN+oHppFsiEZOmxn5/ZJOBvt1uNivrNyvk2nib" + "fmA6W0W0Z6FGkL0sNvLYt0L/aKeLtnqLTvCD2FzRNR53VelSZ8l1IfhfdqqDbRaWcWo6nWZs" + "zrk1SZLVvww40oBeZTG9oDwbOm6pDy8svKHzA9Y2Z3oUgU5WOSIXdOjAv/Nv1hLJ5aZRr+lQ" + "XhNYevSEZye6ZJo2PZNpd9ANXtFFuYtOM0p50UnP4Dm2VeQZavVCZ6cqudziCOL/szSKYkh7" + "hReu62UJv/MyS8+HM2AZuUwNgwcdTlVSkasrrppOgSL0Rg2ME9l6uD8/cqmWd643T0Y90xEO" + "azku48RzCu0vau1z0lNEhJu++Epkoks4dOhjPPaL+Hl+gf4upmlplzaIPGdUIbewXls7OTvo" + "xktbdK6auNyAWz266fOHaoQ4ZKNpOl940vRXHQN99BU9XMkzqylo3+7u0nTEdkHkKzoKRUNH" + "tccxeoEOwxs6XQqK0GdRNFoVKkqEDo+8lk4Syq2UsDWd2ubxKHtm5dzVid1Fe5bOjCx4kUt7" + "mohf8QPCMg3ttGERjp4z1Yk27O5HV15r6MbuVF1S2xbHo+pRNblNf88SaN9sVIpyaSfDYhnU" + "dEQK7A54ELO/f6VJqC2joknF4bmfPgadw4UtY+hmo4Vj9ZanWNtd07PsRfmDuYqTgc43nAei" + "e8TEu4OuWh7kGcb70kViKfR7phdvVA808gw34thHkBzJ+EZHSquH5NfzN5p1ZU+S/kM3fZxE" + "owBnCF2lWFAivcWi8jvUYydDBujAZ0JX7XD5Q0yTMT0EnVf6emHh9FC3anptgu3nc5ueppC2" + "0nfT//OdqRcOA5HeoVsbFGZdFbxFJ3/7C3ShyTPoz/C4PwHnhau+rLmxVdcEwNunRdEYNcFK" + "8Wv6jO4cBL8DfuC1xbqssbVi1TOMN6chHoSu1Ws6ByLZBfDzObEX8yRqNvd2LaZsLzt3k8lY" + "1g8aDw+Ukc3gfatAzMJjapTnOdEb2yqNOtLgSdwkMHBWGEIxxmQS/iYTqvAyGSmN9rZEswZW" + "eJkaC0Af4/ieBrPHY6kWWniCh+0NoVb9DjzbPLhZNhH07f4+CIK4heYbT0V7d0ui3XvcSvLk" + "QEbXROPnz859zFGOLGptZhRDru2UTt9EeB3ISj31TQ784fBIRwVeVTQtrq2gbs93G0Vfvtg9" + "3/Ft92KqdXWfI1cpVCspOOMnjg3QLr0aRwpOxuGOj+i7orD6JarnqVugEE2sfnXs2khx0CsD" + "r6rv33U3w/3MZvNCQ/dP3EI+1c2pa5fGRbfa2e/SApohXbw6RmFk0a2rzMd+ujRclAI0XnYh" + "pBRH1ujfhuil6yZX6LTEmlEnl158H91uFVVtr+t7ne0o9iKXSdSxHjpvLJieqLmXar5xaPfs" + "Ll2mP8IMWqRzTy/PSTplzIvqe7Qndit3gc4581Paq4p23lHpYP+dH6K5lyoZSce1U3+vz0xb" + "dMZLgcD/AB5aode9Qb+/S5VmtEsDUNMH334MxKqpcxw+4/HWbIAuNSbXgc3RLIo+4zNyjeBR" + "ZdbjRKuFKec+F03qqrpCtuieb/uGLGNXyDbdR7mugXsezlUhn06D3iJED+2qvkcDpCpUvzdl" + "nnTVnSg6vavxfT/spb1Wz9qpnPN7Q+mrXTsmF1I+78j0PHpq13jOLR7v966mV6jS0BX0vAJq" + "OZ+3dlLP8UnDH+7nkUpRHMf/399CVFUcf7nu7zj+A8yummsi9EdGAAAAAElFTkSuQmCC") + +#---------------------------------------------------------------------- +whidbey_dock_pane_bottom = PyEmbeddedImage( + "iVBORw0KGgoAAAANSUhEUgAAAF0AAABdCAMAAADwr5rxAAAAAXNSR0IArs4c6QAAAARnQU1B" + "AACxjwv8YQUAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAA" + "AwBQTFRFAAAAECBXFC9vECiQBS2oIDKMIDigC0CVJUqQIkewJFKlJlayMEiwMlSiK2G0JFTR" + "LmnSKGHmKHDoInDwMGjgNnDgNXLwSFeKQFizR2OkUGagaHWVcHiYZ3egZ3e1cHilUHjRQXLw" + "cYCncYa0dpC4R4DwU4PwZobHYIfTcYXGcIjVcpDBc5fWZpHxcZjgcJj3d6Dld6Pwi5Ozl6e3" + "q62xgIjQgpXEgJbUkJjAkJjQgJjggKDXkqHCk6TWhafggqfwh7Dgh7Dwk6Xgk7Tkl7fwoKjA" + "oKjWqLDIp7fXsLDIsbbWoLfnoLjwsLjjl8D4pMLwscHnsMfxtdD0wMHg6tTN4Njk5eTo4Of3" + "5/D48Ofh9/f3AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAaHenigAAAQB0Uk5T////////////////////////////////" + "////////////////////////////////////////////////////////////////////////" + "////////////////////////////////////////////////////////////////////////" + "////////////////////////////////////////////////////////////////////////" + "////////////////////////////////////////////////////////////////////////" + "////////////////////AFP3ByUAAAAYdEVYdFNvZnR3YXJlAFBhaW50Lk5FVCB2My4zNqnn" + "4iUAAAejSURBVGhDpZkLe9o2FIYpTVeWrG3Srmy9stk0LTPJMKMhmYM9KOuSERqydAX8//+H" + "dy6SLF+QRXeepwtg+9XnT+fotlqyRQRey+1vcX9S2+Lm08Bz3f42+C3ond5xt9txnRf2guzp" + "b3u9Y8C77gt7vDW94/t+DwLwrjXelo5wpm+j3pJOcEEHvK33dnSGS7q9eis6wqOIW/D942MP" + "MtPKexs6wlfZsMwcCzopX/2jx6prp76aTp4X6HbeV9I7EUVOexh2vXZ15lTRBTzK+Q70brtd" + "WbUVdAmPVvwKIuIoCkOvWr2ZruCSDlgMog+HleqN9BQu6HHMePEmQdWYY6IPNS+IB3DGSzrg" + "jd4b6KOr6XQsGxA84YyiB27bdTaP95vpoyukS/wmetBuO5vxBu2z2fU1NACBpowh6AvEGL7h" + "S4WYmN3W12hPEqQTXlpOvlNodNcwERpzBtULepTRDl+E9tAEr1gTVNO7xim8olbnc+k9O34F" + "IfwC+WHYMa8PyuiT9JHPSOeunU7PRhTXFJitYWeQ3joqaaiEPpko/ODzZ+n9xcVaxi1n03jc" + "WccKP7sqwRfpi5vr6ZB1DGIAcuYoNH9YLIDewU+DNd06m11diae0dyjQF0hn/CBG+no2u73M" + "wYk/5B9JPXV/VOiFPB3Y5Ol7ghN9uSyyES9/HSRkFZZFHp+jo3JM8fH4/ByrJsOdz+cXEPm2" + "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") + +#---------------------------------------------------------------------- +whidbey_dock_pane_center = PyEmbeddedImage( + "iVBORw0KGgoAAAANSUhEUgAAAF0AAABdCAMAAADwr5rxAAAAAXNSR0IArs4c6QAAAARnQU1B" + "AACxjwv8YQUAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAA" + "AwBQTFRFAAAAGDeAJUqQIEOkIkewJFKlJlayMlSiMle1K2G0LmnSNnDgR2OkQ2O1UGagUHCo" + "VXSzaHWVZ3egZ3e1cHilQ2TAQHDAQnLSVnfFQXfgcYCncYa0dpC4VYTTRIDjVYbgZobHYIfT" + "ZpPXcYXGcpDBc5fWYIjgYZHgcZjgd6Dld6Pwi5Ozq62xgIjQgJbUkJjAkJjQgJjggKDXkqHC" + "k6TWlrDXhafggqfwh7Dwk6Xgk7Tkl7fwoKjWqLDIp7fXsLDIsbbWoLfnoLjwsLjjpMLwscHn" + "sMfxtdD0wMHg6tTN4Njk5eTo4Of35/D48Ofh9/f3AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAXehG6QAAAQB0Uk5T////////////////////////////////" + "////////////////////////////////////////////////////////////////////////" + "////////////////////////////////////////////////////////////////////////" + "////////////////////////////////////////////////////////////////////////" + "////////////////////////////////////////////////////////////////////////" + "////////////////////AFP3ByUAAAAYdEVYdFNvZnR3YXJlAFBhaW50Lk5FVCB2My4zNqnn" + "4iUAAAdqSURBVGhDtZoNW9pIEMc5emrB651VUDRUpEkqb5d6SBOUXq9Q8dprke//aXLzsrvZ" + "hGWz+NzN87RCzP74M5mZzGyspDvYOGh4wx3OTys7nPx+HHjecBf8DnS/3wtD37t47S7InX7V" + "7/cA73mv3fHOdH8wGPTBAO85413pCGf6Luod6QQXdMC7+t6NznBJd1fvREd4kvAnDAa9XgCR" + "6eR7FzrCV3lzjBwHOilf/a3bKnRTX04nn2/Q3XxfSvcTsoL2OA6DdnnklNEFPCn4Hehhu12a" + "tSV0CU9W/BWErZMkjoNy9Xa6gks6YNGIHkWl6q30DC7o6zXjxTcZl9UcGz3SfEE8gDNe0gFv" + "9b2FPlnMZlP5AYInPKPoY6/tXWyv99vpkwXSJX4bfdxuX2zHW7TP5w8P8AFg6JQpGL0Bm8I7" + "/FIxBmbYeI72NEU64aXLye9kGt2z3AitMYPqBT3JaYc3Qntsg5f0BOX00HoLL8nVL1+k79nj" + "CzDhL5Afx769PzDR77Il35DOl3Y2u5mQPZBhtMb+KDt1YvggA/3uTuFH375J33/69CTtK0fT" + "dOo/rRV+vjDgN+nLx4dZxDpGawBy5Cg0v1guge7jq9ETnTqfLxZilfYdNuhLpDN+tEb603z+" + "9a8CnPgRHyT1dPmTjatQpAObfPo7wYn+48cmG/Hy6CglV2FaFPEFOirHEJ9Ob28xazRuFIYt" + "sjjOf9p6RJeeUqCAz9OXGA2YN3DiHzrjw4du66zT6VyCNZvNdzk+hy1XpDw+R18iHXRzJtJF" + "Y4uvrrpdgBP98vJds9vNfqmlXBGv05dIR6+w9tiThLet62tkn50h+xzs8GVL/lIvF0X1Gh3g" + "S/a5oEt8+PYtws/AFP2wxfgfeqkj3ww052R0OE35HGtrFEWTfxBwDUZ6NauDnSq6LNNM1/CK" + "jl8w8znSg4DoEcDZG0U64b9indBukdhuKvWSTsqVV+iOH9zSBe0C3Eiv/3yFJxjoCi/orNxE" + "73YBbqbXiS5vAJn+TD3TMajW2G5hBuHPeBwEQ1wbtjrklfuCoePrtVDgsV3QujWFJzoF1Rqa" + "XNlxafSOoOO1zeye6TWkf8KvvBa3d+w3V8r3SKd0mPJxthVeU9Q+adno9Rco3kwfXAEZ6JwO" + "G/QG6nKhg+cL2nkMgsippFRbUK+uHYKd6G/edDoUiPcmz9TrB3jSjZnef59WqLag0yQd262V" + "pLfedC5L6RMImzXEPK1EjTS+9XrjcQX9Qpkv6NQoKnqzc8lZZNOu6LBUo4dBUJnMRV1hOkXk" + "rnRIqPWMW9iMDmNzoxLfo3Kc6tjvrD0eh1Qhm5BI27XX9qt06YnOSzW673mV+1hMojm/P4+u" + "/A7dJYxtw2ElvRFDbj7e44i0d09F9TL5vba/f6Rrp0yhq0rwiyHG+xVc4swzIpugijnQq0TH" + "dEHPKLraaMFcPcEAkn6X9Dj+CAs/NM/PMedVzMhyg3Vg/6A6xhpsoIuRh+oM4rfSD/P0Q3qP" + "dQbolEzzB+gHcnUGB3HaR+AaifjcRAp3j3iCS3svqV5J7Z37jL63R9IFXZRYogRyi0XUd1JP" + "OxlsRCc8weuQTmwd/gGH9gUd7/SyjcCF+vaQvDeR7/t9nT4mZZNXzC8asKsBnrBB1zYo1H2V" + "8RodEvnPrfRaDei/ERwb8GxZfmMr6wkIr58WBA3qCU5Pi7qJXf2V4Eu8X2rLclsrWj+DeHUa" + "5QPTnwr0GhjQCQ4tQ+bzJImC/HCv92LC97xz1243qMKjvXpVq0n9NchQpRx/6SvlRM9tq+T6" + "SIUPcfdFwZ8mk9oLVMxksJ+OKEnZfAywOB6DFbcl8j2wwPOlydbDq/DgYG8P/6Funa3wAPeK" + "G0KF/p3w6POL16McHfQfHR1Uq9W9XwIKRM2GpB7gG1sSxdnjhIsnJjJNTWDfvxdo2lG8Y8Bo" + "E0IOmbZTNuYmwMtEFuphbjLgl8sbOMrwNPXMG1mbM99JEBwf6zPf48Pi47zAf8QuJUK6GCm9" + "sG3YAN2kp41AwME5OPEBfTGfa/Mq9PmPcBAmQ21ebZg2Ugz0VMHT9PNn3odAgyn57iPYjBta" + "bKz922w4Ne3SmOjaOPuZR0BlPMWLY5BGGl1bpV7a6TxwQXmQeN6F4FacqoZ9G8JKl0Mu0++0" + "75AVFyveRtdHRdHby/5eVjvIvcDkEnHMQseNBTUT5fdS1TtMbcvu0nb6TW6SM+7pJQlIh4K+" + "Vb1Fe6SPclvoAdiztKcp7LxTp0P77+jp/F4qVySZ10b91pjxC3TEczeF/xHc01Jv8wPs8c5d" + "mtLOA0BGL336UZKrqs8xxIzDU7MSOveY2AfmLd8UPSdmeA3jqcvMbAV3C9XOPS+bxKqsQ9bo" + "jk/7yjyjd8g63UW57IEtX87UIa9WpdHCRAftor+nAYhbl//0OZ+cTgQdntW4Ph920p6pR+XQ" + "zrk9oXT0jApMbKRcnpHJ6+ioXcY91haH53s701Pq0mgOtTwCKgSfs3ZQj/kJ5g53i0ihaDgc" + "/n9/C5Gmw+Hxbn/H8S+cD8xcYY4GnAAAAABJRU5ErkJggg==") + +#---------------------------------------------------------------------- +whidbey_dock_pane_left = PyEmbeddedImage( + "iVBORw0KGgoAAAANSUhEUgAAAF0AAABdCAMAAADwr5rxAAAAAXNSR0IArs4c6QAAAARnQU1B" + "AACxjwv8YQUAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAA" + "AwBQTFRFAAAAECBXFC9vECiQBS2oIDKMIDigC0CVJUqQIkewJFKlJlayMEiwMlSiK2G0JFTR" + "LmnSKGHmKHDoInDwMGjgNnDgNXLwSFeKQFizR2OkUGagaHWVcHiYZ3egZ3e1cHilUHjRQXLw" + "cYCncYa0dpC4R4DwU4PwZobHYIfTcYXGcIjVcpDBc5fWZpHxcZjgcJj3d6Dld6Pwi5Ozl6e3" + "q62xgIjQgpXEgJbUkJjAkJjQgKDXkqHCk6TWhafggqfwh7Dgh7Dwk6Xgk7Tkl7fwoKjAoKjW" + "qLDIp7fXsLDIsbbWoLfnoLjwsLjjl8D4pMLwscHnsMfxtdD0wMHg6tTN4Njk5eTo4Of35/D4" + "8Ofh9/f3AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAA+wCLtAAAAQB0Uk5T////////////////////////////////" + "////////////////////////////////////////////////////////////////////////" + "////////////////////////////////////////////////////////////////////////" + "////////////////////////////////////////////////////////////////////////" + "////////////////////////////////////////////////////////////////////////" + "////////////////////AFP3ByUAAAAYdEVYdFNvZnR3YXJlAFBhaW50Lk5FVCB2My4zNqnn" + "4iUAAAeDSURBVGhDtZr/f9JGGMdprZPJnHSKm9OKI4DG0K5kWAptGIw5FGqpdQL5//+Q7Ply" + "d7kkR3L4ms8PWmjyzifP3T33PM+1FO1gQ6/u+DtcH5V2uPh86DmOvwt+B7p7etLpuE7jmb0g" + "e/qr09MTwDvOM3u8Nd3tdrunYIB3rPG2dIQzfRf1lnSCCzrgbX1vR2e4pNurt6IjfDTiJ3S7" + "JycezEwr39vQEb5OmuXMsaCT8vUn3dYdO/XFdPJ5hm7n+0K6OyJLaQ+CjtcsnjlFdAEfpfwO" + "9E6zWbhqC+gSPlrzKwgLR6Mg8IrV59MVXNIBi0b0fr9QfS49hgt6GDJevMmwKObk0fuaL4gH" + "cMZLOuBzfZ9Dv1xMp2P5AMETnlH0odN0Gtvj/Xb65QLpEr+NPmw2G9vxOdrn8+treAAYOmUM" + "Rh/AxvAJXyrAidmpf432KEI64aXLye9kGt3J2Qhz5wyqF/RRQjt8ENqDPHhBTlBM7+Ru4QVr" + "9epK+p49vgAT/gL5QeDm5wcm+n58y2ek89BOp4NLsmsynK2B24svvTQ8yEC/s6/wvc+fpe9n" + "s420W55N47G7CRV+vjDgs/Q7D+/fE/heCECeOQrNPyyXQHfxp96GNM/ni0U/oz5DP3iIdML3" + "QqRv5vPb9yk48fv8Jamn4R9lRiFNPzg8rCJ+j+BEX62ybMTLb3sRuQqXRRqfogP88BDo9/b2" + "LnDVJLhXV1czsPSzwh4NPS2BFD5JP3j0E1j1YeUeqE9SbmZTmikwdQaT5K942nJESuIT9INH" + "TK9WAL+n46+ubm54HqJNJpcaX1tyabxOBzjR0fWofl8ilkudTXHs7Vz+Ug8XafUaHeFEl+ol" + "frlcgmy5pjhKjueMX+mhjnzT1ZwT0w8eVcHwCfg/eqdc/hsBN2BMVNGeMH8pOj1Q7jNdDa/o" + "oDxDr/+LMw/gpDtDJ/wt/k7bIjHdVOolHd2SpFcrv9K7A50jTZoe0FrdGOgKL+jkcyMdRhTi" + "opEe0MzRvcLvEKtnOsJXutXQ7yRtRrNwHG9LIaVltO9xLCDPhPw1m8ITnZSv4M1XT9BqtRXS" + "f8ZbYaEwPZEDfxJ0ws/QaaHY3jHfXCv1SP+OmCl65elTvPWd2PuM9KGznd59BWSgf0/wtPYK" + "wYHO+/YW+i27JqWdyyCYOaXo5csamK79SW1V/fE+rVRc/DRXUnTMmjDduMCLBmb66XlUOm+3" + "k3T0vaLPbOiX8HIhjizmgOh3Kt9OTobDknt8nKCvVjiq1Qprp2wvRzs5XtEBr9E7npem07Tc" + "lQ4SwimnsDEdyuZ66WLUPW63flN+B+3o90qZtctlbvL7sO/58gXJMwnt0LBwgD46Pm614lHd" + "ge6l6MrvkF1C2eb7pWhwDeqBv/qorSapfTIRIcqkHRxL2yAODmqnzJ5GleANH+f7ACLDcbtt" + "pG/y6XWi44JL0FWjBddqf4r4BL1WuV8+Eo5XmjAYyHiDUcY7q+Mltwa6KHkozvTRNxl62Uhn" + "B4RMP8NL5teQDyTiDBbi1EfgGAnqf1+vtSBZk47fvA107R9l0RcMPc9rULBgOsVIESk92WIR" + "8R0KMJ45HCNrSC8/x3sDpotyOPzIrgmQ7hAd9lxtY8HwEJdqcm96MwXntFtM5m21Un5PvkE2" + "1DD0FPyfDdgsPUPXGhRqX33TpXmp6LhrM13ydDrNONrTl7hxyX01qVz6HX3v/onqdfqDB/8Q" + "XvBjegdmeqPxB8GXuLdo9ERrRctnoFWi06uVB/uYEyj1ko4LEfxC8M2mr2/mfS9Z3Ou5mBvg" + "qmo9Qbc/flyt3uX7N5vz875m2LdqsFvQXKV8NAJ6oq2SyCNJfatFdA2OCh1STNZsOs95QAWe" + "B2YIlm5LJHNgUk/0JBwwvn8Ghux6nSJjGg9wJ90QSuXvLvqG4b0kAj6dnTUaDT+Fxge7rD3b" + "kkjXHp0O8GHW38Xag/lfvmSeo77FlQWlTQfWkKmdkqmbmE5RQqiHusmAXy4H8C3DowiGxdQK" + "ytZ8r1+320dcvXHNd3O9eKeydfGcGywJIFcScMQ3DQ1QQ736uv2DLA1B/Rzpi/lcq5cgn4dq" + "AZZoX6tX66ZGioEe/RLXnR8+yGoG65nJ5B2YrJ+whLyILzV1aUz0+I7oA1RLXCuxcRUvvoNl" + "pNG1u9SP+XQuuCAESDp3ITiZp+CS34bIpcsil+mwxSqLg0suPo+ul4q08bCJ9J2kQ0bnmVwi" + "vsuhY2NBJkupXqrqrGLCmNNd2k4fkBtk5Db29FA7Rsyt6nO0w1Ye2xY6xsyv0g67FZ90cP+d" + "8oxEL5XTuSOxro36c+eMm6IjnhME/IfgTh68oKf3IqmdC4CYXnj6UbBWER97Rg0Day/qYOs5" + "gXngST08wXB2UKi8mB4xvps+96C0oPDMqcAz8ELs+/SJ0P90ZsP4DN1GuYVnhPpvdlYm1FMB" + "LTJUu5MyO+3S94IOZzW258PFo0ozVc571A7pnN0Jpa12oR5Phq3OyOTasdQu8RhbLM73dqZH" + "L1A3VQU5R0CpBW+tHdTj+gSzh+/0txC+73+7v4WIIt8/2u3vOP4D32mBB1S/lsMAAAAASUVO" + "RK5CYII=") + +#---------------------------------------------------------------------- +whidbey_dock_pane_right = PyEmbeddedImage( + "iVBORw0KGgoAAAANSUhEUgAAAF0AAABdCAMAAADwr5rxAAAAAXNSR0IArs4c6QAAAARnQU1B" + "AACxjwv8YQUAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAA" + "AwBQTFRFAAAAECBXFC9vECiQBS2oIDKMIDigC0CVJUqQIkewJFKlJlayMEiwMlSiK2G0JFTR" + "LmnSKGHmKHDoInDwMGjgNnDgNXLwSFeKQFizR2OkUGagaHWVcHiYZ3egZ3e1cHilUHjRQXLw" + "cYCncYa0dpC4R4DwU4PwZobHYIfTcYXGcIjVcpDBc5fWZpHxcZjgcJj3d6Dld6Pwi5Ozl6e3" + "q62xgIjQgpXEgJbUkJjAkJjQgJjggKDXkqHCk6TWhafggqfwh7Dgh7Dwk6Xgk7Tkl7fwoKjA" + "oKjWqLDIp7fXsLDIsbbWoLfnoLjwsLjjl8D4pMLwscHnsMfxtdD0wMHg6tTN4Njk5eTo4Of3" + "5/D48Ofh9/f3AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAaHenigAAAQB0Uk5T////////////////////////////////" + "////////////////////////////////////////////////////////////////////////" + "////////////////////////////////////////////////////////////////////////" + "////////////////////////////////////////////////////////////////////////" + "////////////////////////////////////////////////////////////////////////" + "////////////////////AFP3ByUAAAAYdEVYdFNvZnR3YXJlAFBhaW50Lk5FVCB2My4zNqnn" + "4iUAAAe+SURBVGhDtZr/W9pWFMYtW1en67RzunWdpUvQlgWdMCoyJFkotbaRCrMryP//f2Tn" + "yz25N19ILn2enV8Qkn7y5s2555570414jRh4dbezxvnxxhonXww81+2sg1+D3jw7bbWarvPM" + "XpA9/eXZ2SngXfeZPd6a3my322cQgHet8bZ0hDN9HfWWdIIrOuBtvbejM1zo9uqt6AgPAr5C" + "u3166kFmWnlvQ0f4Ih2WmWNBJ+WLf8xYtOzUV9PJ8xzdzvtKejOgyGj3/ZbXqM6cKrqCBxnf" + "gd5qNCpHbQVd4MGCb0HFMgh836tWX05P4EIHLAbRe71K9aV0DVf05ZLx6k4GVTWnjN4zvCAe" + "wBkvdMCXel9CH96Mx6FcQPGUMwl94DZcZ3W9X00f3iBd8Kvog0bDWY0v0R5Ft7dwAQg0JYSg" + "LxAhfMOb8jExW/Uv0R7HSCe8WE6+Uxh0t2QiLM0ZVK/oQUo7fFHa/TJ4RU9QTW+VTuEVY3Uy" + "Ee/Z8RsI5RfI9/1meX9QRB/pf/IJ6fxox+P+kOKWArPVb3b1qT8XXKiAPhol+O6nT+L99fW9" + "xB1nUxg275cJ/rvjV3l8nj6d3Y57fGJ3CUDOnATNf0ynQG/iX917OjU6Pn6Vx+foU6QzvrtE" + "+n0U3b3PwInf4x9JfbR/cHzSzj3iLB3Y5OlrghN9Ps+zES+/duO7h/tMz+IzdFSOKR6Gl5c4" + "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" + "AABJRU5ErkJggg==") + +#---------------------------------------------------------------------- +whidbey_dock_pane_top = PyEmbeddedImage( + "iVBORw0KGgoAAAANSUhEUgAAAF0AAABdCAMAAADwr5rxAAAAAXNSR0IArs4c6QAAAARnQU1B" + "AACxjwv8YQUAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAA" + "AwBQTFRFAAAAECBXFC9vECiQBS2oIDKMIDigC0CVJUqQIkewJFKlJlayMEiwMlSiK2G0JFTR" + "LmnSKGHmKHDoInDwMGjgNnDgNXLwSFeKQFizR2OkUGagaHWVcHiYZ3egZ3e1cHilUHjRQXLw" + "cYCncYa0dpC4R4DwU4PwZobHYIfTcYXGcIjVcpDBc5fWZpHxcZjgcJj3d6Dld6Pwi5Ozl6e3" + "q62xgIjQgpXEgJbUkJjAkJjQgJjggKDXkqHCk6TWhafggqfwh7Dgh7Dwk6Xgk7Tkl7fwoKjA" + "oKjWqLDIp7fXsLDIsbbWoLfnoLjwsLjjl8D4pMLwscHnsMfxtdD0wMHg6tTN4Njk5eTo4Of3" + "5/D48Ofh9/f3AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAaHenigAAAQB0Uk5T////////////////////////////////" + "////////////////////////////////////////////////////////////////////////" + "////////////////////////////////////////////////////////////////////////" + "////////////////////////////////////////////////////////////////////////" + "////////////////////////////////////////////////////////////////////////" + "////////////////////AFP3ByUAAAAYdEVYdFNvZnR3YXJlAFBhaW50Lk5FVCB2My4zNqnn" + "4iUAAAe1SURBVGhDpZkNexJHEMcxrTVNao3G0PoSaXmJ4l1soBhCCUdBmkaPCDFWCN//e1z/" + "M7O7t/fC3aHzPMqFu/3t/2ZnZmeTUrCBnZ83m2cbPB+UNni4NxweHx/9tsGIDei92bBF9B+L" + "44vTu6T8AFb+oTC+ML07gfIjppe/L4ovSofy1nGjIfT9oviC9O6kBZ83flf0ovhi9NccLY2y" + "sv39gvhC9NeT4R/HR5q+Xwa9GL4IvQmvLBcRK4gvQG/+DZcvP8JG2hYF1efTSflxY2HIdLHY" + "gxVwTi696VGCptLz8Xl0Ud4oR90u2vOXNodOyjlYFjoa+VPT89Rn010ol+xXdP1Bq/oI9jA7" + "azPprguX23T4R7QbejY+i/6q1YJXpLYwlZ0foz/4LqOkZdBfvaJgaZQzPbP3IAu/nv7rEYxi" + "g+xgIfVrgX+44FV99HAP7J2dra216jO0/3RULkfpPAP9p+lg37u3Hp65r/oHByE9GvDy/e4u" + "2Bnw7F3bD+kQbGbS1w/y4Dk9wd29vcePtc5dtp9/1rOQ8jt3MjfBnFwlPLtgd2dnG7YD291V" + "88Et2fBU7eNQz2fgiU3kLWVPnhBflHfCRwcpb5GifTw2+M5n4Mkf21vbh7fanvzCPoLy25XB" + "+9MUfJI+v55NuqKjswLw7v3729vPDVounj6ld6Grzi0/6vvTqRplvUOCPie64Dsrot9u3X//" + "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" + "gg==") + +#---------------------------------------------------------------------- +whidbey_left = PyEmbeddedImage( + "iVBORw0KGgoAAAANSUhEUgAAAB4AAAArCAMAAABYWciOAAAAAXNSR0IArs4c6QAAAARnQU1B" + "AACxjwv8YQUAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAA" + "AwBQTFRFAAAAaHWVZ3egZ3e1cHilcYCncYa0dpC4ZobHcYXGcpDBc5fWd6Dld6Pwi5OzgIjQ" + "gJbUkJjQgJjggKDXkqHCk6TWhafggqfwh7Dwk6Xgk7Tkl7fwoKjWqLDIoLfnpMLwscHnsMfx" + "tdD04Njk5/D49/f3AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAQ3JLMwAAAQB0Uk5T////////////////////////////////" + "////////////////////////////////////////////////////////////////////////" + "////////////////////////////////////////////////////////////////////////" + "////////////////////////////////////////////////////////////////////////" + "////////////////////////////////////////////////////////////////////////" + "////////////////////AFP3ByUAAAAYdEVYdFNvZnR3YXJlAFBhaW50Lk5FVCB2My4zNqnn" + "4iUAAAF4SURBVDhPfdTbdoIwEAVQL6htAi0tElBaoVb+/xPTM5M7wc6LLvc6GTIJbnRel/DT" + "ZkUvwXOefsahdqGMJ2LvSwaO4P7d5BdMWejQ9+dzzhOiY4/quu4z44kYSebudbH4REwLm7Q6" + "Jr2hk+lrGR4e7Rb17ZRSdV23v55vxL4vsRCBOesXRrquBbZm0yb7jIHD/EDNKPpUjRBvbmOU" + "Heb7/f6g/aISJhz6R8J4MJvmbM6FGSrPmRaM09i0YZ4zDdHxTN8909I8RMtzyu3NztgwbyxK" + "qytlq6pK0qqROC/0vipGzx0v7ll/MAY2Y1G1SWv9UpXlCvNlpiPZlyjX2w4VJ/rlZk7+D3Oe" + "DsoXwqoNV5HzKHtidJlUC3eXifuXZcxNE91U4xFLIb6jm8oeWIKLcFPN/jxLKY/HhM3+TUl5" + "OhU48eQN9Y6VTwe6D+kLbJ0W3m5X3m926ntgXb7+eg/z2ZzJhcuusN4Lsds9/WfSuhBes94U" + "C6r/AM3yZVcU56/qAAAAAElFTkSuQmCC") + +#---------------------------------------------------------------------- +whidbey_left_single = whidbey_left + +#---------------------------------------------------------------------- +whidbey_left_focus = PyEmbeddedImage( + "iVBORw0KGgoAAAANSUhEUgAAAB4AAAArCAMAAABYWciOAAAAAXNSR0IArs4c6QAAAARnQU1B" + "AACxjwv8YQUAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAA" + "AwBQTFRFAAAAECBXFC9vECiQBS2oIDKMIDigC0CVJUqQIkewJFKlJlayMEiwMlSiK2G0JFTR" + "LmnSKGHmKHDoInDwMGjgNnDgNXLwSFeKQFizR2OkUGagcHiYZ3egcHilUHjRQXLwR4DwU4Pw" + "YIfTcYXGcIjVc5fWZpHxcJj3d6PwgJbUkJjAkJjQkqHChafggqfwh7Dgh7Dwl7fwoKjAqLDI" + "sLDIl8D4pMLw4Of35/D4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAQS37pgAAAQB0Uk5T////////////////////////////////" + "////////////////////////////////////////////////////////////////////////" + "////////////////////////////////////////////////////////////////////////" + "////////////////////////////////////////////////////////////////////////" + "////////////////////////////////////////////////////////////////////////" + "////////////////////AFP3ByUAAAAYdEVYdFNvZnR3YXJlAFBhaW50Lk5FVCB2My4zNqnn" + "4iUAAAGNSURBVDhPfdRrW8IgFAdwLyuXZLIUy67D6dK0pd3w+38xOuewAYOMN3ue/fbnwHZG" + "R8ej5251Yu33nMfcHw8H1iNOxsiNh5xkGUfvmqIBg2YZ8KBrvM3J5BoGH7NBnW9xMjHMGTjl" + "fQYlxvKYh/V5jErc5MEdJxMOAx/BKxZI0w/LkI340aZx5jZzdmdrU92TjKr8IbB2szHKqv1+" + "r2Y4hFDINzWf082A2Xxu+II0TDNU4KcnAcNPz4TiV0P6pp1XKduM9R0XZdlipXBpnNXpgGl3" + "Hu+qdSnzZ1sb0libpab2rqrKMs/d0tqstz+QhwfUt/dabFrrbYXzy1OsNwf0Fgs2TB+ad77B" + "6SNOba9B/uV49D6ZqIvX3bJp1m++mEBO710rLg8wv8wNmVZj6ZdrxeWatmcZW9FjXbxj3ufR" + "6NPr8wLyHnM28vtc6+INX08+w9LTKednwS9I+TwnJg3+UMoTGw3/7wKndxqyXq3gAdg9ZaO0" + "1oab8yo+mRYLKe1p9se5tpCX/7G+dUfhL8vucupsrDz0AAAAAElFTkSuQmCC") + +#---------------------------------------------------------------------- +whidbey_left_focus_single = whidbey_left_focus + +#---------------------------------------------------------------------- +whidbey_right = PyEmbeddedImage( + "iVBORw0KGgoAAAANSUhEUgAAAB4AAAArCAMAAABYWciOAAAAAXNSR0IArs4c6QAAAARnQU1B" + "AACxjwv8YQUAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAA" + "AwBQTFRFAAAAaHWVcHilcYCncYa0dpC4ZobHcYXGcpDBc5fWcZjgd6Dld6Pwi5OzgJbUkJjA" + "kJjQgKDXk6TWhafggqfwh7Dwl7fwp7fXoLfnoLjwsLjjpMLwscHntdD0wMHg4Of35/D49/f3" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAWZqHkAAAAQB0Uk5T////////////////////////////////" + "////////////////////////////////////////////////////////////////////////" + "////////////////////////////////////////////////////////////////////////" + "////////////////////////////////////////////////////////////////////////" + "////////////////////////////////////////////////////////////////////////" + "////////////////////AFP3ByUAAAAYdEVYdFNvZnR3YXJlAFBhaW50Lk5FVCB2My4zNqnn" + "4iUAAAFySURBVDhPddPbeoIwDABglDqoFDcVGBVw8P4PyXJoQ0sxN+L3m0NDzVaJx/YoT5k8" + "9fbAhfve2luS77kfhq5rir077pkTZ34Ng7Vt2yRO/ELuUPeOTIWxdOrA3Fc46p/9AVobsgnm" + "Z0b1xRsTeLa+EV1f+jCBQ+8DlnzgsDBX2fLxYFR8WeYtxJF/u65tF95KM0/TNEv+ZzZfkElL" + "TbKhuDEVnJ/4Z1+cufpmfsBwC47newNV1fV6v8cMTqMx67Jkhs0s3YIRsNbqHDCePczWhVIx" + "S28NoVRdRyxrMaR5zZPjdcDJha+opxOf+33ACthtrR/glkY7LzmXs5npjbn3VqqcFHmE2i0E" + "934+fd9PjKXdvylbR7yn/q7FuVB8HOF9uMJUOsjF3retb9PcysuFZ+aA0QrJJXYzC6/Fk+IO" + "Eee628IOquJcx5wP6nYV9cYvGpYBKucNRqNHpfW+r9+580uS63vjD855vvXcF4fvB7r+A9+i" + "Xf4K/oDaAAAAAElFTkSuQmCC") + +#---------------------------------------------------------------------- +whidbey_right_single = whidbey_right + +#---------------------------------------------------------------------- +whidbey_right_focus = PyEmbeddedImage( + "iVBORw0KGgoAAAANSUhEUgAAAB4AAAArCAMAAABYWciOAAAAAXNSR0IArs4c6QAAAARnQU1B" + "AACxjwv8YQUAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAA" + "AwBQTFRFAAAAECBXFC9vECiQBS2oIDKMIDigC0CVJUqQIkewJFKlJlayMEiwMlSiK2G0JFTR" + "LmnSKGHmKHDoInDwMGjgNnDgNXLwSFeKQFizR2OkUGagcHiYZ3egcHilUHjRQXLwR4DwU4Pw" + "YIfTcYXGcIjVc5fWZpHxcJj3d6PwgJbUkJjAkJjQkqHChafggqfwh7Dgh7Dwl7fwoKjAqLDI" + "sLDIl8D4pMLw4Of35/D4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAQS37pgAAAQB0Uk5T////////////////////////////////" + "////////////////////////////////////////////////////////////////////////" + "////////////////////////////////////////////////////////////////////////" + "////////////////////////////////////////////////////////////////////////" + "////////////////////////////////////////////////////////////////////////" + "////////////////////AFP3ByUAAAAYdEVYdFNvZnR3YXJlAFBhaW50Lk5FVCB2My4zNqnn" + "4iUAAAGXSURBVDhPdZTZVoNADEDpaC2CFSqC1hVaxWoVqdv0/39szDIraF7gcHuTTCankbJx" + "6V7tW2TfTprVmDvcNKsxt7ismnbzOPQ1npaMh5zxNMdo4Afr0CfMNK8Bv4UcMdBzwshDHzBS" + "wlWN6QM/UmKecu68hBj40ed8nmrOuN28u/qR+op9XNfANw+mf8asow31ge8Mh9au4zhlRIF+" + "1z2zjwcTiKWL/f6p2zFHHMdJWkpty77/lpCffcQ3IwzHY5+GitkDG8fTddv/MB2v+9n6dlVJ" + "aBxq9/Dk/l+95IDgu8b3eD0GJ1ibTmYwzqFt12wTLn07xKc51XW16XqaF20D1jPVtRHf3XHn" + "Sxyqm1ovC5r+MZ97OcJEj/TULuA+B3ZRFIdm5njd/o1JaSgmvzK7Bh8LXAt8kku1/8KaAr61" + "u+ZsQ1X0Aauks1tsKdhCzGb4gzMKr66uTTzLFwuNnctjmUycb3t2m6om7JPtu3qZyE+yBURI" + "+UogvwAM5QfUYOw/ybIhtVghPuBUXrg/LiHG1NmwcSNXqV+4tHLqnJPo+QAAAABJRU5ErkJg" + "gg==") + +#---------------------------------------------------------------------- +whidbey_right_focus_single = whidbey_right_focus + +#---------------------------------------------------------------------- +whidbey_up = PyEmbeddedImage( + "iVBORw0KGgoAAAANSUhEUgAAACsAAAAeCAMAAACCNBfsAAAAAXNSR0IArs4c6QAAAARnQU1B" + "AACxjwv8YQUAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAA" + "AwBQTFRFAAAAaHWVcHilcYCncYa0dpC4ZobHcYXGcpDBc5fWcZjgd6Dld6Pwi5OzgJbUkJjA" + "kJjQgKDXk6TWhafggqfwh7Dwl7fwp7fXoLfnoLjwsLjjpMLwscHntdD0wMHg4Of35/D49/f3" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAWZqHkAAAAQB0Uk5T////////////////////////////////" + "////////////////////////////////////////////////////////////////////////" + "////////////////////////////////////////////////////////////////////////" + "////////////////////////////////////////////////////////////////////////" + "////////////////////////////////////////////////////////////////////////" + "////////////////////AFP3ByUAAAAYdEVYdFNvZnR3YXJlAFBhaW50Lk5FVCB2My4zNqnn" + "4iUAAAFmSURBVDhPhdTtdoMgDAZgW3GKWjsL2i/X2vu/SJaEBBXcWf6Jz3l5i9bMpdNXR3Xa" + "Wc/StXNfKXXawaktm1rrUuWHJCWxX01TA1bqkODYlm3bNjCAVYwji9TbneStJcoWcNR5Yz0V" + "mySvLVJrvW/buq7g7NadVxbpvJ3taSyWUuef9cx6kxwsdU3sprPY0tJEucboqginwZapjfqC" + "1UUhT9BboXb28Twfa42pQjLZQMUCwiHbdZKMdqFsPx+PeZee3w2w3WpXugvUY7GAsXPmLvdx" + "HITzXe4QbK8Klbvsckcr+C/bF0WeZ+52ez6Bw+D2AwxdwAxwhRsaPDp9hA4OLWGpSn1pVlZh" + "X8Cg2dpNLlxwrgFKFpP/sRqZf26Ph3T2Te8w3AyijSlJ8fuA1v/Acfy+0Dxp8DyZig2dr9fw" + "WXj5ExoGnxpy5TSi78c0gRUacvE0XjvfsGnqwuryH3q/d6hz07L6CxOEXf5LAPv7AAAAAElF" + "TkSuQmCC") + +#---------------------------------------------------------------------- +whidbey_up_single = whidbey_up + +#---------------------------------------------------------------------- +whidbey_up_focus = PyEmbeddedImage( + "iVBORw0KGgoAAAANSUhEUgAAACsAAAAeCAMAAACCNBfsAAAAAXNSR0IArs4c6QAAAARnQU1B" + "AACxjwv8YQUAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAA" + "AwBQTFRFAAAAECBXFC9vECiQBS2oIDKMIDigC0CVJUqQIkewJFKlJlayMEiwMlSiK2G0JFTR" + "LmnSKGHmKHDoInDwMGjgNnDgNXLwSFeKQFizR2OkUGagcHiYZ3egcHilUHjRQXLwR4DwU4Pw" + "YIfTcYXGcIjVc5fWZpHxcJj3d6PwgJbUkJjAkJjQkqHChafggqfwh7Dgh7Dwl7fwoKjAqLDI" + "sLDIl8D4pMLw4Of35/D4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAQS37pgAAAQB0Uk5T////////////////////////////////" + "////////////////////////////////////////////////////////////////////////" + "////////////////////////////////////////////////////////////////////////" + "////////////////////////////////////////////////////////////////////////" + "////////////////////////////////////////////////////////////////////////" + "////////////////////AFP3ByUAAAAYdEVYdFNvZnR3YXJlAFBhaW50Lk5FVCB2My4zNqnn" + "4iUAAAGTSURBVDhPfdQNV4IwFAZgpEyCVBRZaR8OUcI0yrLw//8xuvfuy22e7jkKZz68uxtg" + "0Pm135fl24XxwB/bNU1VFS/+D77d/TY12lsPe3aLqTkUu3Gxa7cHSC3IsmsHOxZS64pzYTMH" + "23Z7qKFXvpTWwZZd0w5wJivLbHxu14fmtSqUzRhYC5/ZEuY/tVbZ2NjyA1o9/UB9qmrtZG0x" + "teKtdnjSplCmDWXLd7xZF63G0opUzux2Ra5eoLCYShvQqv2io7IymewGUsV9lVYdcG1TqAnd" + "QbSbDbR6bqETkastYbCruob5xTNAhpp27PgK7WqFG8DZvz2kY8DBQwGF68XKW/HUtPCBE1rb" + "dJKCjOMwDLq7gjHbkscvZUEOBiGtLc+NtTdYjCcJyFDsQ2cshOnr1PlYUmH7aTqbqYyEajRS" + "12Bqr6f2V2CaLInjCCqGShJ5NTRAVOQSRokulDWfozap2gLGmaMwetIv7/yeulGpxnb94TCK" + "Hp23fLHAedSgeS/C4fHo/y89R5qqfhF9+xJGvszoH5Xccuo6pVT3AAAAAElFTkSuQmCC") + +#---------------------------------------------------------------------- +whidbey_up_focus_single = whidbey_up_focus + +#---------------------------------------------------------------------- + +whidbey_denied = aero_denied + +#---------------------------------------------------------------------- + +# ------------------------ # +# - AuiToolBar Constants - # +# ------------------------ # + +ITEM_CONTROL = wx.ITEM_MAX +""" The item in the AuiToolBar is a control. """ +ITEM_LABEL = ITEM_CONTROL + 1 +""" The item in the AuiToolBar is a text label. """ +ITEM_SPACER = ITEM_CONTROL + 2 +""" The item in the AuiToolBar is a spacer. """ +ITEM_SEPARATOR = wx.ITEM_SEPARATOR +""" The item in the AuiToolBar is a separator. """ +ITEM_CHECK = wx.ITEM_CHECK +""" The item in the AuiToolBar is a toolbar check item. """ +ITEM_NORMAL = wx.ITEM_NORMAL +""" The item in the AuiToolBar is a standard toolbar item. """ +ITEM_RADIO = wx.ITEM_RADIO +""" The item in the AuiToolBar is a toolbar radio item. """ +ID_RESTORE_FRAME = wx.ID_HIGHEST + 10000 +""" Identifier for restoring a minimized pane. """ + +BUTTON_DROPDOWN_WIDTH = 10 +""" Width of the drop-down button in AuiToolBar. """ + +DISABLED_TEXT_GREY_HUE = 153.0 +""" Hue text colour for the disabled text in AuiToolBar. """ +DISABLED_TEXT_COLOUR = wx.Colour(DISABLED_TEXT_GREY_HUE, + DISABLED_TEXT_GREY_HUE, + DISABLED_TEXT_GREY_HUE) +""" Text colour for the disabled text in AuiToolBar. """ + +AUI_TB_TEXT = 1 << 0 +""" Shows the text in the toolbar buttons; by default only icons are shown. """ +AUI_TB_NO_TOOLTIPS = 1 << 1 +""" Don't show tooltips on `AuiToolBar` items. """ +AUI_TB_NO_AUTORESIZE = 1 << 2 +""" Do not auto-resize the `AuiToolBar`. """ +AUI_TB_GRIPPER = 1 << 3 +""" Shows a gripper on the `AuiToolBar`. """ +AUI_TB_OVERFLOW = 1 << 4 +""" The `AuiToolBar` can contain overflow items. """ +AUI_TB_VERTICAL = 1 << 5 +""" The `AuiToolBar` is vertical. """ +AUI_TB_HORZ_LAYOUT = 1 << 6 +""" Shows the text and the icons alongside, not vertically stacked. +This style must be used with ``AUI_TB_TEXT``. """ +AUI_TB_PLAIN_BACKGROUND = 1 << 7 +""" Don't draw a gradient background on the toolbar. """ +AUI_TB_CLOCKWISE = 1 << 8 +AUI_TB_COUNTERCLOCKWISE = 1 << 9 + +AUI_TB_HORZ_TEXT = AUI_TB_HORZ_LAYOUT | AUI_TB_TEXT +""" Combination of ``AUI_TB_HORZ_LAYOUT`` and ``AUI_TB_TEXT``. """ +AUI_TB_VERT_TEXT = AUI_TB_VERTICAL | AUI_TB_CLOCKWISE | AUI_TB_TEXT + +AUI_TB_DEFAULT_STYLE = 0 +""" `AuiToolBar` default style. """ + +# AuiToolBar settings +AUI_TBART_SEPARATOR_SIZE = 0 +""" Separator size in AuiToolBar. """ +AUI_TBART_GRIPPER_SIZE = 1 +""" Gripper size in AuiToolBar. """ +AUI_TBART_OVERFLOW_SIZE = 2 +""" Overflow button size in AuiToolBar. """ + +# AuiToolBar text orientation +AUI_TBTOOL_TEXT_LEFT = 0 # unused/unimplemented +""" Text in AuiToolBar items is aligned left. """ +AUI_TBTOOL_TEXT_RIGHT = 1 +""" Text in AuiToolBar items is aligned right. """ +AUI_TBTOOL_TEXT_TOP = 2 # unused/unimplemented +""" Text in AuiToolBar items is aligned top. """ +AUI_TBTOOL_TEXT_BOTTOM = 3 +""" Text in AuiToolBar items is aligned bottom. """ + +# AuiToolBar tool orientation +AUI_TBTOOL_HORIZONTAL = 0 # standard +AUI_TBTOOL_VERT_CLOCKWISE = 1 # rotation of 90 on the right +AUI_TBTOOL_VERT_COUNTERCLOCKWISE = 2 # rotation of 90 on the left + + +# --------------------- # +# - AuiMDI* Constants - # +# --------------------- # + +wxWINDOWCLOSE = 4001 +""" Identifier for the AuiMDI "close window" menu. """ +wxWINDOWCLOSEALL = 4002 +""" Identifier for the AuiMDI "close all windows" menu. """ +wxWINDOWNEXT = 4003 +""" Identifier for the AuiMDI "next window" menu. """ +wxWINDOWPREV = 4004 +""" Identifier for the AuiMDI "previous window" menu. """ + +# ----------------------------- # +# - AuiDockingGuide Constants - # +# ----------------------------- # + +colourTargetBorder = wx.Colour(180, 180, 180) +colourTargetShade = wx.Colour(206, 206, 206) +colourTargetBackground = wx.Colour(224, 224, 224) +colourIconBorder = wx.Colour(82, 65, 156) +colourIconBackground = wx.Colour(255, 255, 255) +colourIconDockingPart1 = wx.Colour(215, 228, 243) +colourIconDockingPart2 = wx.Colour(180, 201, 225) +colourIconShadow = wx.Colour(198, 198, 198) +colourIconArrow = wx.Colour(77, 79, 170) +colourHintBackground = wx.Colour(0, 64, 255) +guideSizeX, guideSizeY = 29, 32 +aeroguideSizeX, aeroguideSizeY = 31, 32 +whidbeySizeX, whidbeySizeY = 43, 30 + +# ------------------------------- # +# - AuiSwitcherDialog Constants - # +# ------------------------------- # + +SWITCHER_TEXT_MARGIN_X = 4 +SWITCHER_TEXT_MARGIN_Y = 1 diff --git a/agw/aui/aui_constants.pyc b/agw/aui/aui_constants.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b2e3f0b4b4f8eed986def3f7630dbc059499ffd3 GIT binary patch literal 127728 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�=$wX9oc3>u^9}f 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 literal 0 HcmV?d00001 diff --git a/agw/aui/aui_switcherdialog.py b/agw/aui/aui_switcherdialog.py new file mode 100644 index 0000000..552cb84 --- /dev/null +++ b/agw/aui/aui_switcherdialog.py @@ -0,0 +1,1216 @@ +""" +Description +=========== + +The idea of `SwitcherDialog` is to make it easier to implement keyboard +navigation in AUI and other applications that have multiple panes and +tabs. + +A key combination with a modifier (such as ``Ctrl`` + ``Tab``) shows the +dialog, and the user holds down the modifier whilst navigating with +``Tab`` and arrow keys before releasing the modifier to dismiss the dialog +and activate the selected pane. + +The switcher dialog is a multi-column menu with no scrolling, implemented +by the `MultiColumnListCtrl` class. You can have headings for your items +for logical grouping, and you can force a column break if you need to. + +The modifier used for invoking and dismissing the dialog can be customised, +as can the colours, number of rows, and the key used for cycling through +the items. So you can use different keys on different platforms if +required (especially since ``Ctrl`` + ``Tab`` is reserved on some platforms). + +Items are shown as names and optional 16x16 images. + + +Base Functionalities +==================== + +To use the dialog, you set up the items in a `SwitcherItems` object, +before passing this to the `SwitcherDialog` instance. + +Call L{SwitcherItems.AddItem} and optionally L{SwitcherItems.AddGroup} to add items and headings. These +functions take a label (to be displayed to the user), an identifying name, +an integer id, and a bitmap. The name and id are purely for application-defined +identification. You may also set a description to be displayed when each +item is selected; and you can set a window pointer for convenience when +activating the desired window after the dialog returns. + +Have created the dialog, you call `ShowModal()`, and if the return value is +``wx.ID_OK``, retrieve the selection from the dialog and activate the pane. + +The sample code below shows a generic method of finding panes and notebook +tabs within the current L{AuiManager}, and using the pane name or notebook +tab position to display the pane. + +The only other code to add is a menu item with the desired accelerator, +whose modifier matches the one you pass to L{SwitcherDialog.SetModifierKey} +(the default being ``wx.WXK_CONTROL``). + + +Usage +===== + +Menu item:: + + if wx.Platform == "__WXMAC__": + switcherAccel = "Alt+Tab" + elif wx.Platform == "__WXGTK__": + switcherAccel = "Ctrl+/" + else: + switcherAccel = "Ctrl+Tab" + + view_menu.Append(ID_SwitchPane, _("S&witch Window...") + "\t" + switcherAccel) + + +Event handler:: + + def OnSwitchPane(self, event): + + items = SwitcherItems() + items.SetRowCount(12) + + # Add the main windows and toolbars, in two separate columns + # We'll use the item 'id' to store the notebook selection, or -1 if not a page + + for k in xrange(2): + if k == 0: + items.AddGroup(_("Main Windows"), "mainwindows") + else: + items.AddGroup(_("Toolbars"), "toolbars").BreakColumn() + + for pane in self._mgr.GetAllPanes(): + name = pane.name + caption = pane.caption + + toolbar = isinstance(info.window, wx.ToolBar) or isinstance(info.window, aui.AuiToolBar) + if caption and (toolBar and k == 1) or (not toolBar and k == 0): + items.AddItem(caption, name, -1).SetWindow(pane.window) + + # Now add the wxAuiNotebook pages + + items.AddGroup(_("Notebook Pages"), "pages").BreakColumn() + + for pane in self._mgr.GetAllPanes(): + nb = pane.window + if isinstance(nb, aui.AuiNotebook): + for j in xrange(nb.GetPageCount()): + + name = nb.GetPageText(j) + win = nb.GetPage(j) + + items.AddItem(name, name, j, nb.GetPageBitmap(j)).SetWindow(win) + + # Select the focused window + + idx = items.GetIndexForFocus() + if idx != wx.NOT_FOUND: + items.SetSelection(idx) + + if wx.Platform == "__WXMAC__": + items.SetBackgroundColour(wx.WHITE) + + # Show the switcher dialog + + dlg = SwitcherDialog(items, wx.GetApp().GetTopWindow()) + + # In GTK+ we can't use Ctrl+Tab; we use Ctrl+/ instead and tell the switcher + # to treat / in the same was as tab (i.e. cycle through the names) + + if wx.Platform == "__WXGTK__": + dlg.SetExtraNavigationKey(wxT('/')) + + if wx.Platform == "__WXMAC__": + dlg.SetBackgroundColour(wx.WHITE) + dlg.SetModifierKey(wx.WXK_ALT) + + ans = dlg.ShowModal() + + if ans == wx.ID_OK and dlg.GetSelection() != -1: + item = items.GetItem(dlg.GetSelection()) + + if item.GetId() == -1: + info = self._mgr.GetPane(item.GetName()) + info.Show() + self._mgr.Update() + info.window.SetFocus() + + else: + nb = item.GetWindow().GetParent() + win = item.GetWindow(); + if isinstance(nb, aui.AuiNotebook): + nb.SetSelection(item.GetId()) + win.SetFocus() + + +""" + +import wx + +import auibook +from aui_utilities import FindFocusDescendant +from aui_constants import SWITCHER_TEXT_MARGIN_X, SWITCHER_TEXT_MARGIN_Y + + +# Define a translation function +_ = wx.GetTranslation + + +class SwitcherItem(object): + """ An object containing information about one item. """ + + def __init__(self, item=None): + """ Default class constructor. """ + + self._id = 0 + self._isGroup = False + self._breakColumn = False + self._rowPos = 0 + self._colPos = 0 + self._window = None + self._description = "" + + self._textColour = wx.NullColour + self._bitmap = wx.NullBitmap + self._font = wx.NullFont + + if item: + self.Copy(item) + + + def Copy(self, item): + """ + Copy operator between 2 L{SwitcherItem} instances. + + :param `item`: another instance of L{SwitcherItem}. + """ + + self._id = item._id + self._name = item._name + self._title = item._title + self._isGroup = item._isGroup + self._breakColumn = item._breakColumn + self._rect = item._rect + self._font = item._font + self._textColour = item._textColour + self._bitmap = item._bitmap + self._description = item._description + self._rowPos = item._rowPos + self._colPos = item._colPos + self._window = item._window + + + def SetTitle(self, title): + + self._title = title + return self + + + def GetTitle(self): + + return self._title + + + def SetName(self, name): + + self._name = name + return self + + + def GetName(self): + + return self._name + + + def SetDescription(self, descr): + + self._description = descr + return self + + + def GetDescription(self): + + return self._description + + + def SetId(self, id): + + self._id = id + return self + + + def GetId(self): + + return self._id + + + def SetIsGroup(self, isGroup): + + self._isGroup = isGroup + return self + + + def GetIsGroup(self): + + return self._isGroup + + + def BreakColumn(self, breakCol=True): + + self._breakColumn = breakCol + return self + + + def GetBreakColumn(self): + + return self._breakColumn + + + def SetRect(self, rect): + + self._rect = rect + return self + + + def GetRect(self): + + return self._rect + + + def SetTextColour(self, colour): + + self._textColour = colour + return self + + + def GetTextColour(self): + + return self._textColour + + + def SetFont(self, font): + + self._font = font + return self + + + def GetFont(self): + + return self._font + + + def SetBitmap(self, bitmap): + + self._bitmap = bitmap + return self + + + def GetBitmap(self): + + return self._bitmap + + + def SetRowPos(self, pos): + + self._rowPos = pos + return self + + + def GetRowPos(self): + + return self._rowPos + + + def SetColPos(self, pos): + + self._colPos = pos + return self + + + def GetColPos(self): + + return self._colPos + + + def SetWindow(self, win): + + self._window = win + return self + + + def GetWindow(self): + + return self._window + + +class SwitcherItems(object): + """ An object containing switcher items. """ + + def __init__(self, items=None): + """ Default class constructor. """ + + self._selection = -1 + self._rowCount = 10 + self._columnCount = 0 + + self._backgroundColour = wx.NullColour + self._textColour = wx.NullColour + self._selectionColour = wx.NullColour + self._selectionOutlineColour = wx.NullColour + self._itemFont = wx.NullFont + + self._items = [] + + if wx.Platform == "__WXMSW__": + # If on Windows XP/Vista, use more appropriate colours + self.SetSelectionOutlineColour(wx.Colour(49, 106, 197)) + self.SetSelectionColour(wx.Colour(193, 210, 238)) + + if items: + self.Copy(items) + + + def Copy(self, items): + """ + Copy operator between 2 L{SwitcherItems}. + + :param `items`: another instance of L{SwitcherItems}. + """ + + self.Clear() + + for item in items._items: + self._items.append(item) + + self._selection = items._selection + self._rowCount = items._rowCount + self._columnCount = items._columnCount + + self._backgroundColour = items._backgroundColour + self._textColour = items._textColour + self._selectionColour = items._selectionColour + self._selectionOutlineColour = items._selectionOutlineColour + self._itemFont = items._itemFont + + + def AddItem(self, titleOrItem, name=None, id=0, bitmap=wx.NullBitmap): + + if isinstance(titleOrItem, SwitcherItem): + self._items.append(titleOrItem) + return self._items[-1] + + item = SwitcherItem() + item.SetTitle(titleOrItem) + item.SetName(name) + item.SetId(id) + item.SetBitmap(bitmap) + + self._items.append(item) + return self._items[-1] + + + def AddGroup(self, title, name, id=0, bitmap=wx.NullBitmap): + + item = self.AddItem(title, name, id, bitmap) + item.SetIsGroup(True) + + return item + + + def Clear(self): + + self._items = [] + + + def FindItemByName(self, name): + + for i in xrange(len(self._items)): + if self._items[i].GetName() == name: + return i + + return wx.NOT_FOUND + + + def FindItemById(self, id): + + for i in xrange(len(self._items)): + if self._items[i].GetId() == id: + return i + + return wx.NOT_FOUND + + + def SetSelection(self, sel): + + self._selection = sel + + + def SetSelectionByName(self, name): + + idx = self.FindItemByName(name) + if idx != wx.NOT_FOUND: + self.SetSelection(idx) + + + def GetSelection(self): + + return self._selection + + + def GetItem(self, i): + + return self._items[i] + + + def GetItemCount(self): + + return len(self._items) + + + def SetRowCount(self, rows): + + self._rowCount = rows + + + def GetRowCount(self): + + return self._rowCount + + + def SetColumnCount(self, cols): + + self._columnCount = cols + + + def GetColumnCount(self): + + return self._columnCount + + + def SetBackgroundColour(self, colour): + + self._backgroundColour = colour + + + def GetBackgroundColour(self): + + return self._backgroundColour + + + def SetTextColour(self, colour): + + self._textColour = colour + + + def GetTextColour(self): + + return self._textColour + + + def SetSelectionColour(self, colour): + + self._selectionColour = colour + + + def GetSelectionColour(self): + + return self._selectionColour + + + def SetSelectionOutlineColour(self, colour): + + self._selectionOutlineColour = colour + + + def GetSelectionOutlineColour(self): + + return self._selectionOutlineColour + + + def SetItemFont(self, font): + + self._itemFont = font + + + def GetItemFont(self): + + return self._itemFont + + + def PaintItems(self, dc, win): + + backgroundColour = wx.SystemSettings.GetColour(wx.SYS_COLOUR_3DFACE) + standardTextColour = wx.SystemSettings.GetColour(wx.SYS_COLOUR_WINDOWTEXT) + selectionColour = wx.SystemSettings.GetColour(wx.SYS_COLOUR_HIGHLIGHT) + selectionOutlineColour = wx.SystemSettings.GetColour(wx.SYS_COLOUR_WINDOWTEXT) + standardFont = wx.SystemSettings.GetFont(wx.SYS_DEFAULT_GUI_FONT) + groupFont = wx.SystemSettings.GetFont(wx.SYS_DEFAULT_GUI_FONT) + groupFont.SetWeight(wx.BOLD) + + if self.GetBackgroundColour().IsOk(): + backgroundColour = self.GetBackgroundColour() + + if self.GetTextColour().IsOk(): + standardTextColour = self.GetTextColour() + + if self.GetSelectionColour().IsOk(): + selectionColour = self.GetSelectionColour() + + if self.GetSelectionOutlineColour().IsOk(): + selectionOutlineColour = self.GetSelectionOutlineColour() + + if self.GetItemFont().IsOk(): + + standardFont = self.GetItemFont() + groupFont = wx.Font(standardFont.GetPointSize(), standardFont.GetFamily(), standardFont.GetStyle(), + wx.BOLD, standardFont.GetUnderlined(), standardFont.GetFaceName()) + + textMarginX = SWITCHER_TEXT_MARGIN_X + + dc.SetLogicalFunction(wx.COPY) + dc.SetBrush(wx.Brush(backgroundColour)) + dc.SetPen(wx.TRANSPARENT_PEN) + dc.DrawRectangleRect(win.GetClientRect()) + dc.SetBackgroundMode(wx.TRANSPARENT) + + for i in xrange(len(self._items)): + item = self._items[i] + if i == self._selection: + dc.SetPen(wx.Pen(selectionOutlineColour)) + dc.SetBrush(wx.Brush(selectionColour)) + dc.DrawRectangleRect(item.GetRect()) + + clippingRect = wx.Rect(*item.GetRect()) + clippingRect.Deflate(1, 1) + + dc.SetClippingRect(clippingRect) + + if item.GetTextColour().IsOk(): + dc.SetTextForeground(item.GetTextColour()) + else: + dc.SetTextForeground(standardTextColour) + + if item.GetFont().IsOk(): + dc.SetFont(item.GetFont()) + else: + if item.GetIsGroup(): + dc.SetFont(groupFont) + else: + dc.SetFont(standardFont) + + w, h = dc.GetTextExtent(item.GetTitle()) + x = item.GetRect().x + + x += textMarginX + + if not item.GetIsGroup(): + if item.GetBitmap().IsOk() and item.GetBitmap().GetWidth() <= 16 \ + and item.GetBitmap().GetHeight() <= 16: + x -= textMarginX + dc.DrawBitmap(item.GetBitmap(), x, item.GetRect().y + \ + (item.GetRect().height - item.GetBitmap().GetHeight())/2, + True) + x += 16 + textMarginX + #x += textMarginX + + y = item.GetRect().y + (item.GetRect().height - h)/2 + dc.DrawText(item.GetTitle(), x, y) + dc.DestroyClippingRegion() + + + def CalculateItemSize(self, dc): + + # Start off allowing for an icon + sz = wx.Size(150, 16) + standardFont = wx.SystemSettings.GetFont(wx.SYS_DEFAULT_GUI_FONT) + groupFont = wx.SystemSettings.GetFont(wx.SYS_DEFAULT_GUI_FONT) + groupFont.SetWeight(wx.BOLD) + + textMarginX = SWITCHER_TEXT_MARGIN_X + textMarginY = SWITCHER_TEXT_MARGIN_Y + maxWidth = 300 + maxHeight = 40 + + if self.GetItemFont().IsOk(): + standardFont = self.GetItemFont() + + for item in self._items: + if item.GetFont().IsOk(): + dc.SetFont(item.GetFont()) + else: + if item.GetIsGroup(): + dc.SetFont(groupFont) + else: + dc.SetFont(standardFont) + + w, h = dc.GetTextExtent(item.GetTitle()) + w += 16 + 2*textMarginX + + if w > sz.x: + sz.x = min(w, maxWidth) + if h > sz.y: + sz.y = min(h, maxHeight) + + if sz == wx.Size(16, 16): + sz = wx.Size(100, 25) + else: + sz.x += textMarginX*2 + sz.y += textMarginY*2 + + return sz + + + def GetIndexForFocus(self): + + for i, item in enumerate(self._items): + if item.GetWindow(): + + if FindFocusDescendant(item.GetWindow()): + return i + + return wx.NOT_FOUND + + +class MultiColumnListCtrl(wx.PyControl): + """ A control for displaying several columns (not scrollable). """ + + def __init__(self, parent, aui_manager, id=wx.ID_ANY, pos=wx.DefaultPosition, size=wx.DefaultSize, + style=0, validator=wx.DefaultValidator, name="MultiColumnListCtrl"): + + wx.PyControl.__init__(self, parent, id, pos, size, style, validator, name) + + self._overallSize = wx.Size(200, 100) + self._modifierKey = wx.WXK_CONTROL + self._extraNavigationKey = 0 + self._aui_manager = aui_manager + + self.SetInitialSize(size) + self.SetBackgroundStyle(wx.BG_STYLE_CUSTOM) + + self.Bind(wx.EVT_PAINT, self.OnPaint) + self.Bind(wx.EVT_ERASE_BACKGROUND, self.OnEraseBackground) + self.Bind(wx.EVT_MOUSE_EVENTS, self.OnMouseEvent) + self.Bind(wx.EVT_CHAR, self.OnChar) + self.Bind(wx.EVT_KEY_DOWN, self.OnKey) + self.Bind(wx.EVT_KEY_UP, self.OnKey) + + + def __del__(self): + + self._aui_manager.HideHint() + + + def DoGetBestSize(self): + + return self._overallSize + + + def OnEraseBackground(self, event): + + pass + + + def OnPaint(self, event): + + dc = wx.AutoBufferedPaintDC(self) + rect = self.GetClientRect() + + if self._items.GetColumnCount() == 0: + self.CalculateLayout(dc) + + if self._items.GetColumnCount() == 0: + return + + self._items.PaintItems(dc, self) + + + def OnMouseEvent(self, event): + + if event.LeftDown(): + self.SetFocus() + + + def OnChar(self, event): + + event.Skip() + + + def OnKey(self, event): + + if event.GetEventType() == wx.wxEVT_KEY_UP: + if event.GetKeyCode() == self.GetModifierKey(): + topLevel = wx.GetTopLevelParent(self) + closeEvent = wx.CloseEvent(wx.wxEVT_CLOSE_WINDOW, topLevel.GetId()) + closeEvent.SetEventObject(topLevel) + closeEvent.SetCanVeto(False) + + topLevel.GetEventHandler().ProcessEvent(closeEvent) + return + + event.Skip() + return + + keyCode = event.GetKeyCode() + + if keyCode in [wx.WXK_ESCAPE, wx.WXK_RETURN]: + if keyCode == wx.WXK_ESCAPE: + self._items.SetSelection(-1) + + topLevel = wx.GetTopLevelParent(self) + closeEvent = wx.CloseEvent(wx.wxEVT_CLOSE_WINDOW, topLevel.GetId()) + closeEvent.SetEventObject(topLevel) + closeEvent.SetCanVeto(False) + + topLevel.GetEventHandler().ProcessEvent(closeEvent) + return + + elif keyCode in [wx.WXK_TAB, self.GetExtraNavigationKey()]: + if event.ShiftDown(): + + self._items.SetSelection(self._items.GetSelection() - 1) + if self._items.GetSelection() < 0: + self._items.SetSelection(self._items.GetItemCount() - 1) + + self.AdvanceToNextSelectableItem(-1) + + else: + + self._items.SetSelection(self._items.GetSelection() + 1) + if self._items.GetSelection() >= self._items.GetItemCount(): + self._items.SetSelection(0) + + self.AdvanceToNextSelectableItem(1) + + self.GenerateSelectionEvent() + self.Refresh() + + elif keyCode in [wx.WXK_DOWN, wx.WXK_NUMPAD_DOWN]: + self._items.SetSelection(self._items.GetSelection() + 1) + if self._items.GetSelection() >= self._items.GetItemCount(): + self._items.SetSelection(0) + + self.AdvanceToNextSelectableItem(1) + self.GenerateSelectionEvent() + self.Refresh() + + elif keyCode in [wx.WXK_UP, wx.WXK_NUMPAD_UP]: + self._items.SetSelection(self._items.GetSelection() - 1) + if self._items.GetSelection() < 0: + self._items.SetSelection(self._items.GetItemCount() - 1) + + self.AdvanceToNextSelectableItem(-1) + self.GenerateSelectionEvent() + self.Refresh() + + elif keyCode in [wx.WXK_HOME, wx.WXK_NUMPAD_HOME]: + self._items.SetSelection(0) + self.AdvanceToNextSelectableItem(1) + self.GenerateSelectionEvent() + self.Refresh() + + elif keyCode in [wx.WXK_END, wx.WXK_NUMPAD_END]: + self._items.SetSelection(self._items.GetItemCount() - 1) + self.AdvanceToNextSelectableItem(-1) + self.GenerateSelectionEvent() + self.Refresh() + + elif keyCode in [wx.WXK_LEFT, wx.WXK_NUMPAD_LEFT]: + item = self._items.GetItem(self._items.GetSelection()) + + row = item.GetRowPos() + newCol = item.GetColPos() - 1 + if newCol < 0: + newCol = self._items.GetColumnCount() - 1 + + # Find the first item from the end whose row matches and whose column is equal or lower + for i in xrange(self._items.GetItemCount()-1, -1, -1): + item2 = self._items.GetItem(i) + if item2.GetColPos() == newCol and item2.GetRowPos() <= row: + self._items.SetSelection(i) + break + + self.AdvanceToNextSelectableItem(-1) + self.GenerateSelectionEvent() + self.Refresh() + + elif keyCode in [wx.WXK_RIGHT, wx.WXK_NUMPAD_RIGHT]: + item = self._items.GetItem(self._items.GetSelection()) + + row = item.GetRowPos() + newCol = item.GetColPos() + 1 + if newCol >= self._items.GetColumnCount(): + newCol = 0 + + # Find the first item from the end whose row matches and whose column is equal or lower + for i in xrange(self._items.GetItemCount()-1, -1, -1): + item2 = self._items.GetItem(i) + if item2.GetColPos() == newCol and item2.GetRowPos() <= row: + self._items.SetSelection(i) + break + + self.AdvanceToNextSelectableItem(1) + self.GenerateSelectionEvent() + self.Refresh() + + else: + event.Skip() + + + def AdvanceToNextSelectableItem(self, direction): + + if self._items.GetItemCount() < 2: + return + + if self._items.GetSelection() == -1: + self._items.SetSelection(0) + + oldSel = self._items.GetSelection() + + while 1: + + if self._items.GetItem(self._items.GetSelection()).GetIsGroup(): + + self._items.SetSelection(self._items.GetSelection() + direction) + if self._items.GetSelection() == -1: + self._items.SetSelection(self._items.GetItemCount()-1) + elif self._items.GetSelection() == self._items.GetItemCount(): + self._items.SetSelection(0) + if self._items.GetSelection() == oldSel: + break + + else: + break + + self.SetTransparency() + selection = self._items.GetItem(self._items.GetSelection()).GetWindow() + pane = self._aui_manager.GetPane(selection) + + if not pane.IsOk(): + if isinstance(selection.GetParent(), auibook.AuiNotebook): + self.SetTransparency(selection) + self._aui_manager.ShowHint(selection.GetScreenRect()) + wx.CallAfter(self.SetFocus) + self.SetFocus() + return + else: + self._aui_manager.HideHint() + return + if not pane.IsShown(): + self._aui_manager.HideHint() + return + + self.SetTransparency(selection) + self._aui_manager.ShowHint(selection.GetScreenRect()) + # NOTE: this is odd but it is the only way for the focus to + # work correctly on wxMac... + wx.CallAfter(self.SetFocus) + self.SetFocus() + + + def SetTransparency(self, selection=None): + + if not self.GetParent().CanSetTransparent(): + return + + if selection is not None: + intersects = False + if selection.GetScreenRect().Intersects(self.GetParent().GetScreenRect()): + intersects = True + self.GetParent().SetTransparent(200) + return + + self.GetParent().SetTransparent(255) + + + def GenerateSelectionEvent(self): + + event = wx.CommandEvent(wx.wxEVT_COMMAND_LISTBOX_SELECTED, self.GetId()) + event.SetEventObject(self) + event.SetInt(self._items.GetSelection()) + self.GetEventHandler().ProcessEvent(event) + + + def CalculateLayout(self, dc=None): + + if dc is None: + dc = wx.ClientDC(self) + + if self._items.GetSelection() == -1: + self._items.SetSelection(0) + + columnCount = 1 + + # Spacing between edge of window or between columns + xMargin = 4 + yMargin = 4 + + # Inter-row spacing + rowSpacing = 2 + + itemSize = self._items.CalculateItemSize(dc) + self._overallSize = wx.Size(350, 200) + + currentRow = 0 + x = xMargin + y = yMargin + + breaking = False + i = 0 + + while 1: + + oldOverallSize = self._overallSize + item = self._items.GetItem(i) + + item.SetRect(wx.Rect(x, y, itemSize.x, itemSize.y)) + item.SetColPos(columnCount-1) + item.SetRowPos(currentRow) + + if item.GetRect().GetBottom() > self._overallSize.y: + self._overallSize.y = item.GetRect().GetBottom() + yMargin + + if item.GetRect().GetRight() > self._overallSize.x: + self._overallSize.x = item.GetRect().GetRight() + xMargin + + currentRow += 1 + + y += rowSpacing + itemSize.y + stopBreaking = breaking + + if currentRow > self._items.GetRowCount() or (item.GetBreakColumn() and not breaking and currentRow != 1): + currentRow = 0 + columnCount += 1 + x += xMargin + itemSize.x + y = yMargin + + # Make sure we don't orphan a group + if item.GetIsGroup() or (item.GetBreakColumn() and not breaking): + self._overallSize = oldOverallSize + + if item.GetBreakColumn(): + breaking = True + + # Repeat the last item, in the next column + i -= 1 + + if stopBreaking: + breaking = False + + i += 1 + + if i >= self._items.GetItemCount(): + break + + self._items.SetColumnCount(columnCount) + self.InvalidateBestSize() + + + def SetItems(self, items): + + self._items = items + + + def GetItems(self): + + return self._items + + + def SetExtraNavigationKey(self, keyCode): + """ + Set an extra key that can be used to cycle through items, + in case not using the ``Ctrl`` + ``Tab`` combination. + """ + + self._extraNavigationKey = keyCode + + + def GetExtraNavigationKey(self): + + return self._extraNavigationKey + + + def SetModifierKey(self, modifierKey): + """ + Set the modifier used to invoke the dialog, and therefore to test for + release. + """ + + self._modifierKey = modifierKey + + + def GetModifierKey(self): + + return self._modifierKey + + + +class SwitcherDialog(wx.Dialog): + """ + SwitcherDialog shows a L{MultiColumnListCtrl} with a list of panes + and tabs for the user to choose. ``Ctrl`` + ``Tab`` cycles through them. + """ + + def __init__(self, items, parent, aui_manager, id=wx.ID_ANY, title=_("Pane Switcher"), pos=wx.DefaultPosition, + size=wx.DefaultSize, style=wx.STAY_ON_TOP|wx.DIALOG_NO_PARENT|wx.BORDER_SIMPLE): + """ Default class constructor. """ + + self._switcherBorderStyle = (style & wx.BORDER_MASK) + if self._switcherBorderStyle == wx.BORDER_NONE: + self._switcherBorderStyle = wx.BORDER_SIMPLE + + style &= wx.BORDER_MASK + style |= wx.BORDER_NONE + + wx.Dialog.__init__(self, parent, id, title, pos, size, style) + + self._listCtrl = MultiColumnListCtrl(self, aui_manager, + style=wx.WANTS_CHARS|wx.NO_BORDER) + self._listCtrl.SetItems(items) + self._listCtrl.CalculateLayout() + + self._descriptionCtrl = wx.html.HtmlWindow(self, size=(-1, 100), style=wx.BORDER_NONE) + self._descriptionCtrl.SetBackgroundColour(self.GetBackgroundColour()) + + if wx.Platform == "__WXGTK__": + fontSize = 11 + self._descriptionCtrl.SetStandardFonts(fontSize) + + sizer = wx.BoxSizer(wx.VERTICAL) + self.SetSizer(sizer) + sizer.Add(self._listCtrl, 1, wx.ALL|wx.EXPAND, 10) + sizer.Add(self._descriptionCtrl, 0, wx.ALL|wx.EXPAND, 10) + sizer.SetSizeHints(self) + + self._listCtrl.SetFocus() + + self.Centre(wx.BOTH) + + if self._listCtrl.GetItems().GetSelection() == -1: + self._listCtrl.GetItems().SetSelection(0) + + self._listCtrl.AdvanceToNextSelectableItem(1) + + self.ShowDescription(self._listCtrl.GetItems().GetSelection()) + + self.Bind(wx.EVT_CLOSE, self.OnCloseWindow) + self.Bind(wx.EVT_ACTIVATE, self.OnActivate) + self.Bind(wx.EVT_LISTBOX, self.OnSelectItem) + self.Bind(wx.EVT_PAINT, self.OnPaint) + + # Attributes + self._closing = False + if wx.Platform == "__WXMSW__": + self._borderColour = wx.Colour(49, 106, 197) + else: + self._borderColour = wx.BLACK + + self._aui_manager = aui_manager + + + def OnCloseWindow(self, event): + + if self._closing: + return + + if self.IsModal(): + self._closing = True + + if self.GetSelection() == -1: + self.EndModal(wx.ID_CANCEL) + else: + self.EndModal(wx.ID_OK) + + self._aui_manager.HideHint() + + + def GetSelection(self): + + return self._listCtrl.GetItems().GetSelection() + + + def OnActivate(self, event): + + if not event.GetActive(): + if not self._closing: + self._closing = True + self.EndModal(wx.ID_CANCEL) + + + def OnPaint(self, event): + + dc = wx.PaintDC(self) + + if self._switcherBorderStyle == wx.BORDER_SIMPLE: + + dc.SetPen(wx.Pen(self._borderColour)) + dc.SetBrush(wx.TRANSPARENT_BRUSH) + + rect = self.GetClientRect() + dc.DrawRectangleRect(rect) + + # Draw border around the HTML control + rect = wx.Rect(*self._descriptionCtrl.GetRect()) + rect.Inflate(1, 1) + dc.DrawRectangleRect(rect) + + + def OnSelectItem(self, event): + + self.ShowDescription(event.GetSelection()) + + +# Convert a colour to a 6-digit hex string + def ColourToHexString(self, col): + + hx = '%02x%02x%02x' % tuple([int(c) for c in col]) + return hx + + + def ShowDescription(self, i): + + item = self._listCtrl.GetItems().GetItem(i) + colour = self._listCtrl.GetItems().GetBackgroundColour() + + if not colour.IsOk(): + colour = self.GetBackgroundColour() + + backgroundColourHex = self.ColourToHexString(colour) + html = _("") + item.GetTitle() + _("") + + if item.GetDescription(): + html += _("

    ") + html += item.GetDescription() + + html += _("") + self._descriptionCtrl.SetPage(html) + + + def SetExtraNavigationKey(self, keyCode): + + self._extraNavigationKey = keyCode + if self._listCtrl: + self._listCtrl.SetExtraNavigationKey(keyCode) + + + def GetExtraNavigationKey(self): + + return self._extraNavigationKey + + + def SetModifierKey(self, modifierKey): + + self._modifierKey = modifierKey + if self._listCtrl: + self._listCtrl.SetModifierKey(modifierKey) + + + def GetModifierKey(self): + + return self._modifierKey + + + def SetBorderColour(self, colour): + + self._borderColour = colour + + \ No newline at end of file diff --git a/agw/aui/aui_utilities.py b/agw/aui/aui_utilities.py new file mode 100644 index 0000000..d6c701a --- /dev/null +++ b/agw/aui/aui_utilities.py @@ -0,0 +1,678 @@ +""" +This module contains some common functions used by wxPython-AUI to +manipulate colours, bitmaps, text, gradient shadings and custom +dragging images for AuiNotebook tabs. +""" + +__author__ = "Andrea Gavana " +__date__ = "31 March 2009" + + +import wx + +from aui_constants import * + + +if wx.Platform == "__WXMAC__": + import Carbon.Appearance + + +def BlendColour(fg, bg, alpha): + """ + Blends the two colour component `fg` and `bg` into one colour component, adding + an optional alpha channel. + + :param `fg`: the first colour component; + :param `bg`: the second colour component; + :param `alpha`: an optional transparency value. + """ + + result = bg + (alpha*(fg - bg)) + + if result < 0.0: + result = 0.0 + if result > 255: + result = 255 + + return result + + +def StepColour(c, ialpha): + """ + Darken/lighten the input colour `c`. + + :param `c`: a colour to darken/lighten; + :param `ialpha`: a transparency value. + """ + + if ialpha == 100: + return c + + r, g, b = c.Red(), c.Green(), c.Blue() + + # ialpha is 0..200 where 0 is completely black + # and 200 is completely white and 100 is the same + # convert that to normal alpha 0.0 - 1.0 + ialpha = min(ialpha, 200) + ialpha = max(ialpha, 0) + alpha = (ialpha - 100.0)/100.0 + + if ialpha > 100: + + # blend with white + bg = 255 + alpha = 1.0 - alpha # 0 = transparent fg 1 = opaque fg + + else: + + # blend with black + bg = 0 + alpha = 1.0 + alpha # 0 = transparent fg 1 = opaque fg + + r = BlendColour(r, bg, alpha) + g = BlendColour(g, bg, alpha) + b = BlendColour(b, bg, alpha) + + return wx.Colour(r, g, b) + + +def LightContrastColour(c): + """ + Creates a new, lighter colour based on the input colour `c`. + + :param `c`: the input colour to analyze. + """ + + amount = 120 + + # if the colour is especially dark, then + # make the contrast even lighter + if c.Red() < 128 and c.Green() < 128 and c.Blue() < 128: + amount = 160 + + return StepColour(c, amount) + + +def ChopText(dc, text, max_size): + """ + Chops the input `text` if its size does not fit in `max_size`, by cutting the + text and adding ellipsis at the end. + + :param `dc`: a `wx.DC` device context; + :param `text`: the text to chop; + :param `max_size`: the maximum size in which the text should fit. + """ + + # first check if the text fits with no problems + x, y, dummy = dc.GetMultiLineTextExtent(text) + + if x <= max_size: + return text + + textLen = len(text) + last_good_length = 0 + + for i in xrange(textLen, -1, -1): + s = text[0:i] + s += "..." + + x, y = dc.GetTextExtent(s) + last_good_length = i + + if x < max_size: + break + + ret = text[0:last_good_length] + "..." + return ret + + +def BitmapFromBits(bits, w, h, colour): + """ + BitmapFromBits() is a utility function that creates a + masked bitmap from raw bits (XBM format). + + :param `bits`: a string containing the raw bits of the bitmap; + :param `w`: the bitmap width; + :param `h`: the bitmap height; + :param `colour`: the colour which will replace all white pixels in the + raw bitmap. + """ + + img = wx.BitmapFromBits(bits, w, h).ConvertToImage() + img.Replace(0, 0, 0, 123, 123, 123) + img.Replace(255, 255, 255, colour.Red(), colour.Green(), colour.Blue()) + img.SetMaskColour(123, 123, 123) + return wx.BitmapFromImage(img) + + +def IndentPressedBitmap(rect, button_state): + """ + Indents the input rectangle `rect` based on the value of `button_state`. + + :param `rect`: an instance of wx.Rect; + :param `button_state`: an L{AuiNotebook} button state. + """ + + if button_state == AUI_BUTTON_STATE_PRESSED: + rect.x += 1 + rect.y += 1 + + return rect + + +def GetBaseColour(): + """ + Returns the face shading colour on push buttons/backgrounds, mimicking as closely + as possible the platform UI colours. + """ + + if wx.Platform == "__WXMAC__": + + if hasattr(wx, 'MacThemeColour'): + base_colour = wx.MacThemeColour(Carbon.Appearance.kThemeBrushToolbarBackground) + else: + brush = wx.Brush(wx.BLACK) + brush.MacSetTheme(Carbon.Appearance.kThemeBrushToolbarBackground) + base_colour = brush.GetColour() + + else: + + base_colour = wx.SystemSettings.GetColour(wx.SYS_COLOUR_3DFACE) + + # the base_colour is too pale to use as our base colour, + # so darken it a bit + if ((255-base_colour.Red()) + + (255-base_colour.Green()) + + (255-base_colour.Blue()) < 60): + + base_colour = StepColour(base_colour, 92) + + return base_colour + + +def MakeDisabledBitmap(bitmap): + """ + Convert the given image (in place) to a grayed-out version, + appropriate for a 'disabled' appearance. + + :param `bitmap`: the bitmap to gray-out. + """ + + anImage = bitmap.ConvertToImage() + factor = 0.7 # 0 < f < 1. Higher Is Grayer + + if anImage.HasMask(): + maskColour = (anImage.GetMaskRed(), anImage.GetMaskGreen(), anImage.GetMaskBlue()) + else: + maskColour = None + + data = map(ord, list(anImage.GetData())) + + for i in range(0, len(data), 3): + + pixel = (data[i], data[i+1], data[i+2]) + pixel = MakeGray(pixel, factor, maskColour) + + for x in range(3): + data[i+x] = pixel[x] + + anImage.SetData(''.join(map(chr, data))) + + return anImage.ConvertToBitmap() + + +def MakeGray(rgbTuple, factor, maskColour): + """ + Make a pixel grayed-out. If the pixel matches the `maskColour`, it won't be + changed. + + :param `rgbTuple`: a tuple representing a pixel colour; + :param `factor`: a graying-out factor; + :param `maskColour`: a colour mask. + """ + + if rgbTuple != maskColour: + r, g, b = rgbTuple + return map(lambda x: int((230 - x) * factor) + x, (r, g, b)) + else: + return rgbTuple + + +def Clip(a, b, c): + """ + Clips the value in `a` based on the extremes `b` and `c`. + + :param `a`: the value to analyze; + :param `b`: a minimum value; + :param `c`: a maximum value. + """ + + return ((a < b and [b]) or [(a > c and [c] or [a])[0]])[0] + + +def LightColour(colour, percent): + """ + Brighten input `colour` by `percent`. + + :param `colour`: the colour to be brightened; + :param `percent`: brightening percentage. + """ + + end_colour = wx.WHITE + + rd = end_colour.Red() - colour.Red() + gd = end_colour.Green() - colour.Green() + bd = end_colour.Blue() - colour.Blue() + + high = 100 + + # We take the percent way of the colour from colour -. white + i = percent + r = colour.Red() + ((i*rd*100)/high)/100 + g = colour.Green() + ((i*gd*100)/high)/100 + b = colour.Blue() + ((i*bd*100)/high)/100 + return wx.Colour(r, g, b) + + +def PaneCreateStippleBitmap(): + """ + Creates a stipple bitmap to be used in a `wx.Brush`. + This is used to draw sash resize hints. + """ + + data = [0, 0, 0, 192, 192, 192, 192, 192, 192, 0, 0, 0] + img = wx.EmptyImage(2, 2) + counter = 0 + + for ii in xrange(2): + for jj in xrange(2): + img.SetRGB(ii, jj, data[counter], data[counter+1], data[counter+2]) + counter = counter + 3 + + return img.ConvertToBitmap() + + +def DrawMACCloseButton(colour, backColour=None): + """ + Draws the wxMAC tab close button using `wx.GraphicsContext`. + + :param `colour`: the colour to use to draw the circle; + :param `backColour`: the optional background colour for the circle. + """ + + bmp = wx.EmptyBitmapRGBA(16, 16) + dc = wx.MemoryDC() + dc.SelectObject(bmp) + + gc = wx.GraphicsContext.Create(dc) + gc.SetBrush(wx.Brush(colour)) + path = gc.CreatePath() + path.AddCircle(6.5, 7, 6.5) + path.CloseSubpath() + gc.FillPath(path) + + path = gc.CreatePath() + if backColour is not None: + pen = wx.Pen(backColour, 2) + else: + pen = wx.Pen("white", 2) + + pen.SetCap(wx.CAP_BUTT) + pen.SetJoin(wx.JOIN_BEVEL) + gc.SetPen(pen) + path.MoveToPoint(3.5, 4) + path.AddLineToPoint(9.5, 10) + path.MoveToPoint(3.5, 10) + path.AddLineToPoint(9.5, 4) + path.CloseSubpath() + gc.DrawPath(path) + + dc.SelectObject(wx.NullBitmap) + return bmp + + +def DarkenBitmap(bmp, caption_colour, new_colour): + """ + Darkens the input bitmap on wxMAC using the input colour. + + :param `bmp`: the bitmap to be manipulated; + :param `caption_colour`: the colour of the pane caption; + :param `new_colour`: the colour used to darken the bitmap. + """ + + image = bmp.ConvertToImage() + red = caption_colour.Red()/float(new_colour.Red()) + green = caption_colour.Green()/float(new_colour.Green()) + blue = caption_colour.Blue()/float(new_colour.Blue()) + image = image.AdjustChannels(red, green, blue) + return image.ConvertToBitmap() + + +def DrawGradientRectangle(dc, rect, start_colour, end_colour, direction, offset=0, length=0): + """ + Draws a gradient-shaded rectangle. + + :param `dc`: a `wx.DC` device context; + :param `rect`: the rectangle in which to draw the gradient; + :param `start_colour`: the first colour of the gradient; + :param `end_colour`: the second colour of the gradient; + :param `direction`: the gradient direction (horizontal or vertical). + """ + + if direction == AUI_GRADIENT_VERTICAL: + dc.GradientFillLinear(rect, start_colour, end_colour, wx.SOUTH) + else: + dc.GradientFillLinear(rect, start_colour, end_colour, wx.EAST) + + +def FindFocusDescendant(ancestor): + """ + Find a window with the focus, that is also a descendant of the given window. + This is used to determine the window to initially send commands to. + + :param `ancestor`: the window to check for ancestry. + """ + + # Process events starting with the window with the focus, if any. + focusWin = wx.Window.FindFocus() + win = focusWin + + # Check if this is a descendant of this frame. + # If not, win will be set to NULL. + while win: + if win == ancestor: + break + else: + win = win.GetParent() + + if win is None: + focusWin = None + + return focusWin + + +def GetLabelSize(dc, label, vertical): + """ + Returns the L{AuiToolBar} item label size. + + :param `label`: the toolbar tool label; + :param `vertical`: whether the toolbar tool orientation is vertical or not. + """ + + text_width = text_height = 0 + + # get the text height + dummy, text_height = dc.GetTextExtent("ABCDHgj") + # get the text width + if label.strip(): + text_width, dummy = dc.GetTextExtent(label) + + if vertical: + tmp = text_height + text_height = text_width + text_width = tmp + + return wx.Size(text_width, text_height) + + +#--------------------------------------------------------------------------- +# TabDragImage implementation +# This class handles the creation of a custom image when dragging +# AuiNotebook tabs +#--------------------------------------------------------------------------- + +class TabDragImage(wx.DragImage): + """ + This class handles the creation of a custom image in case of drag and + drop of a notebook tab. + """ + + def __init__(self, notebook, page, button_state, tabArt): + """ + Default class constructor. + + For internal use: do not call it in your code! + + :param `notebook`: an instance of L{AuiNotebook}; + :param `page`: the dragged L{AuiNotebook} page; + :param `button_state`: the state of the close button on the tab; + :param `tabArt`: an instance of L{AuiDefaultTabArt} or one of its derivations. + """ + + self._backgroundColour = wx.NamedColour("pink") + self._bitmap = self.CreateBitmap(notebook, page, button_state, tabArt) + wx.DragImage.__init__(self, self._bitmap) + + + def CreateBitmap(self, notebook, page, button_state, tabArt): + """ + Actually creates the drag and drop bitmap. + + :param `notebook`: an instance of L{AuiNotebook}; + :param `page`: the dragged L{AuiNotebook} page; + :param `button_state`: the state of the close button on the tab; + :param `tabArt`: an instance of L{AuiDefaultTabArt} or one of its derivations. + """ + + control = page.control + memory = wx.MemoryDC(wx.EmptyBitmap(1, 1)) + + tab_size, x_extent = tabArt.GetTabSize(memory, notebook, page.caption, page.bitmap, page.active, + button_state, control) + + tab_width, tab_height = tab_size + rect = wx.Rect(0, 0, tab_width, tab_height) + + bitmap = wx.EmptyBitmap(tab_width+1, tab_height+1) + memory.SelectObject(bitmap) + + if wx.Platform == "__WXMAC__": + memory.SetBackground(wx.TRANSPARENT_BRUSH) + else: + memory.SetBackground(wx.Brush(self._backgroundColour)) + + memory.SetBackgroundMode(wx.TRANSPARENT) + memory.Clear() + + paint_control = wx.Platform != "__WXMAC__" + tabArt.DrawTab(memory, notebook, page, rect, button_state, paint_control=paint_control) + + memory.SetBrush(wx.TRANSPARENT_BRUSH) + memory.SetPen(wx.BLACK_PEN) + memory.DrawRoundedRectangle(0, 0, tab_width+1, tab_height+1, 2) + + memory.SelectObject(wx.NullBitmap) + + # Gtk and Windows unfortunatly don't do so well with transparent + # drawing so this hack corrects the image to have a transparent + # background. + if wx.Platform != '__WXMAC__': + timg = bitmap.ConvertToImage() + if not timg.HasAlpha(): + timg.InitAlpha() + for y in xrange(timg.GetHeight()): + for x in xrange(timg.GetWidth()): + pix = wx.Colour(timg.GetRed(x, y), + timg.GetGreen(x, y), + timg.GetBlue(x, y)) + if pix == self._backgroundColour: + timg.SetAlpha(x, y, 0) + bitmap = timg.ConvertToBitmap() + return bitmap + + +def GetDockingImage(direction, useAero, center): + """ + Returns the correct name of the docking bitmap depending on the input parameters. + + :param `useAero`: whether L{AuiManager} is using Aero-style or Whidbey-style docking + images or not; + :param `center`: whether we are looking for the center diamond-shaped bitmap or not. + """ + + suffix = (center and [""] or ["_single"])[0] + prefix = "" + if useAero == 2: + # Whidbey docking guides + prefix = "whidbey_" + elif useAero == 1: + # Aero docking style + prefix = "aero_" + + if direction == wx.TOP: + bmp_unfocus = eval("%sup%s"%(prefix, suffix)).GetBitmap() + bmp_focus = eval("%sup_focus%s"%(prefix, suffix)).GetBitmap() + elif direction == wx.BOTTOM: + bmp_unfocus = eval("%sdown%s"%(prefix, suffix)).GetBitmap() + bmp_focus = eval("%sdown_focus%s"%(prefix, suffix)).GetBitmap() + elif direction == wx.LEFT: + bmp_unfocus = eval("%sleft%s"%(prefix, suffix)).GetBitmap() + bmp_focus = eval("%sleft_focus%s"%(prefix, suffix)).GetBitmap() + elif direction == wx.RIGHT: + bmp_unfocus = eval("%sright%s"%(prefix, suffix)).GetBitmap() + bmp_focus = eval("%sright_focus%s"%(prefix, suffix)).GetBitmap() + else: + bmp_unfocus = eval("%stab%s"%(prefix, suffix)).GetBitmap() + bmp_focus = eval("%stab_focus%s"%(prefix, suffix)).GetBitmap() + + return bmp_unfocus, bmp_focus + + +def TakeScreenShot(rect): + """ + Takes a screenshot of the screen at given position and size (rect). + + :param `rect`: the screen rectangle for which we want to take a screenshot. + """ + + # Create a DC for the whole screen area + dcScreen = wx.ScreenDC() + + # Create a Bitmap that will later on hold the screenshot image + # Note that the Bitmap must have a size big enough to hold the screenshot + # -1 means using the current default colour depth + bmp = wx.EmptyBitmap(rect.width, rect.height) + + # Create a memory DC that will be used for actually taking the screenshot + memDC = wx.MemoryDC() + + # Tell the memory DC to use our Bitmap + # all drawing action on the memory DC will go to the Bitmap now + memDC.SelectObject(bmp) + + # Blit (in this case copy) the actual screen on the memory DC + # and thus the Bitmap + memDC.Blit( 0, # Copy to this X coordinate + 0, # Copy to this Y coordinate + rect.width, # Copy this width + rect.height, # Copy this height + dcScreen, # From where do we copy? + rect.x, # What's the X offset in the original DC? + rect.y # What's the Y offset in the original DC? + ) + + # Select the Bitmap out of the memory DC by selecting a new + # uninitialized Bitmap + memDC.SelectObject(wx.NullBitmap) + + return bmp + + +def RescaleScreenShot(bmp, thumbnail_size=200): + """ + Rescales a bitmap to be 300 pixels wide (or tall) at maximum. + + :param `bmp`: the bitmap to rescale; + :param `thumbnail_size`: the maximum size of every page thumbnail. + """ + + bmpW, bmpH = bmp.GetWidth(), bmp.GetHeight() + img = bmp.ConvertToImage() + + newW, newH = bmpW, bmpH + + if bmpW > bmpH: + if bmpW > thumbnail_size: + ratio = bmpW/float(thumbnail_size) + newW, newH = int(bmpW/ratio), int(bmpH/ratio) + img.Rescale(newW, newH, wx.IMAGE_QUALITY_HIGH) + else: + if bmpH > thumbnail_size: + ratio = bmpH/float(thumbnail_size) + newW, newH = int(bmpW/ratio), int(bmpH/ratio) + img.Rescale(newW, newH, wx.IMAGE_QUALITY_HIGH) + + newBmp = img.ConvertToBitmap() + otherBmp = wx.EmptyBitmap(newW+5, newH+5) + + memDC = wx.MemoryDC() + memDC.SelectObject(otherBmp) + memDC.SetBackground(wx.WHITE_BRUSH) + memDC.Clear() + + memDC.SetPen(wx.TRANSPARENT_PEN) + + pos = 0 + for i in xrange(5, 0, -1): + brush = wx.Brush(wx.Colour(50*i, 50*i, 50*i)) + memDC.SetBrush(brush) + memDC.DrawRoundedRectangle(0, 0, newW+5-pos, newH+5-pos, 2) + pos += 1 + + memDC.DrawBitmap(newBmp, 0, 0, True) + + # Select the Bitmap out of the memory DC by selecting a new + # uninitialized Bitmap + memDC.SelectObject(wx.NullBitmap) + + return otherBmp + + +def GetSlidingPoints(rect, size, direction): + """ + Returns the point at which the sliding in and out of a minimized pane begins. + + :param `rect`: the L{AuiToolBar} tool screen rectangle; + :param `size`: the pane window size; + :param `direction`: the pane docking direction. + """ + + if direction == AUI_DOCK_LEFT: + startX, startY = rect.x + rect.width + 2, rect.y + elif direction == AUI_DOCK_TOP: + startX, startY = rect.x, rect.y + rect.height + 2 + elif direction == AUI_DOCK_RIGHT: + startX, startY = rect.x - size.x - 2, rect.y + elif direction == AUI_DOCK_BOTTOM: + startX, startY = rect.x, rect.y - size.y - 2 + else: + raise Exception("How did we get here?") + + caption_height = wx.SystemSettings.GetMetric(wx.SYS_CAPTION_Y) + frame_border_x = wx.SystemSettings.GetMetric(wx.SYS_FRAMESIZE_X) + frame_border_y = wx.SystemSettings.GetMetric(wx.SYS_FRAMESIZE_Y) + + stopX = size.x + caption_height + frame_border_x + stopY = size.x + frame_border_y + + return startX, startY, stopX, stopY + + +def CopyAttributes(newArt, oldArt): + """ + Copies pens, brushes, colours and fonts from the old tab art to the new one. + + :param `newArt`: the new instance of L{AuiDefaultTabArt}; + :param `oldArt`: the old instance of L{AuiDefaultTabArt}. + """ + + attrs = dir(oldArt) + + for attr in attrs: + if attr.startswith("_") and (attr.endswith("_colour") or attr.endswith("_font") or \ + attr.endswith("_font") or attr.endswith("_brush") or \ + attr.endswith("Pen") or attr.endswith("_pen")): + setattr(newArt, attr, getattr(oldArt, attr)) + + return newArt + diff --git a/agw/aui/aui_utilities.pyc b/agw/aui/aui_utilities.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9d9b75f4be8bbd84f289f042425188f1521deccc GIT binary patch literal 19758 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 literal 0 HcmV?d00001 diff --git a/agw/aui/auibar.py b/agw/aui/auibar.py new file mode 100644 index 0000000..4d348b7 --- /dev/null +++ b/agw/aui/auibar.py @@ -0,0 +1,3926 @@ +""" +auibar contains an implementation of L{AuiToolBar}, which is a completely owner-drawn +toolbar perfectly integrated with the AUI layout system. This allows drag and drop of +toolbars, docking/floating behaviour and the possibility to define "overflow" items +in the toolbar itself. + +The default theme that is used is L{AuiDefaultToolBarArt}, which provides a modern, +glossy look and feel. The theme can be changed by calling L{AuiToolBar.SetArtProvider}. +""" + +__author__ = "Andrea Gavana " +__date__ = "31 March 2009" + + +import wx +import types + +from aui_utilities import BitmapFromBits, StepColour, GetLabelSize +from aui_utilities import GetBaseColour, MakeDisabledBitmap + +import framemanager +from aui_constants import * + +# wxPython version string +_VERSION_STRING = wx.VERSION_STRING + +# AuiToolBar events +wxEVT_COMMAND_AUITOOLBAR_TOOL_DROPDOWN = wx.NewEventType() +wxEVT_COMMAND_AUITOOLBAR_OVERFLOW_CLICK = wx.NewEventType() +wxEVT_COMMAND_AUITOOLBAR_RIGHT_CLICK = wx.NewEventType() +wxEVT_COMMAND_AUITOOLBAR_MIDDLE_CLICK = wx.NewEventType() +wxEVT_COMMAND_AUITOOLBAR_BEGIN_DRAG = wx.NewEventType() + +EVT_AUITOOLBAR_TOOL_DROPDOWN = wx.PyEventBinder(wxEVT_COMMAND_AUITOOLBAR_TOOL_DROPDOWN, 1) +""" A dropdown `AuiToolBarItem` is being shown. """ +EVT_AUITOOLBAR_OVERFLOW_CLICK = wx.PyEventBinder(wxEVT_COMMAND_AUITOOLBAR_OVERFLOW_CLICK, 1) +""" The user left-clicked on the overflow button in `AuiToolBar`. """ +EVT_AUITOOLBAR_RIGHT_CLICK = wx.PyEventBinder(wxEVT_COMMAND_AUITOOLBAR_RIGHT_CLICK, 1) +""" Fires an event when the user right-clicks on a `AuiToolBarItem`. """ +EVT_AUITOOLBAR_MIDDLE_CLICK = wx.PyEventBinder(wxEVT_COMMAND_AUITOOLBAR_MIDDLE_CLICK, 1) +""" Fires an event when the user middle-clicks on a `AuiToolBarItem`. """ +EVT_AUITOOLBAR_BEGIN_DRAG = wx.PyEventBinder(wxEVT_COMMAND_AUITOOLBAR_BEGIN_DRAG, 1) +""" A drag operation involving a toolbar item has started. """ + +# ---------------------------------------------------------------------- + +class CommandToolBarEvent(wx.PyCommandEvent): + """ A specialized command event class for events sent by L{AuiToolBar}. """ + + def __init__(self, command_type, win_id): + """ + Default class constructor. + + :param `command_type`: the event kind or an instance of `wx.PyCommandEvent`. + :param `win_id`: the window identification number. + """ + + if type(command_type) == types.IntType: + wx.PyCommandEvent.__init__(self, command_type, win_id) + else: + wx.PyCommandEvent.__init__(self, command_type.GetEventType(), command_type.GetId()) + + self.is_dropdown_clicked = False + self.click_pt = wx.Point(-1, -1) + self.rect = wx.Rect(-1, -1, 0, 0) + self.tool_id = -1 + + + def IsDropDownClicked(self): + """ Returns whether the drop down menu has been clicked. """ + + return self.is_dropdown_clicked + + + def SetDropDownClicked(self, c): + """ + Sets whether the drop down menu has been clicked. + + :param `c`: ``True`` to set the drop down as clicked, ``False`` otherwise. + """ + + self.is_dropdown_clicked = c + + + def GetClickPoint(self): + """ Returns the point where the user clicked with the mouse. """ + + return self.click_pt + + + def SetClickPoint(self, p): + """ + Sets the clicking point. + + :param `p`: a `wx.Point` object. + """ + + self.click_pt = p + + + def GetItemRect(self): + """ Returns the L{AuiToolBarItem} rectangle. """ + + return self.rect + + + def SetItemRect(self, r): + """ + Sets the L{AuiToolBarItem} rectangle. + + :param `r`: an instance of `wx.Rect`. + """ + + self.rect = r + + + def GetToolId(self): + """ Returns the L{AuiToolBarItem} identifier. """ + + return self.tool_id + + + def SetToolId(self, id): + """ + Sets the L{AuiToolBarItem} identifier. + + :param `id`: the toolbar item identifier. + """ + + self.tool_id = id + + +# ---------------------------------------------------------------------- + +class AuiToolBarEvent(CommandToolBarEvent): + """ A specialized command event class for events sent by L{AuiToolBar}. """ + + def __init__(self, command_type=None, win_id=0): + """ + Default class constructor. + + :param `command_type`: the event kind or an instance of `wx.PyCommandEvent`. + :param `win_id`: the window identification number. + """ + + CommandToolBarEvent.__init__(self, command_type, win_id) + + if type(command_type) == types.IntType: + self.notify = wx.NotifyEvent(command_type, win_id) + else: + self.notify = wx.NotifyEvent(command_type.GetEventType(), command_type.GetId()) + + + def GetNotifyEvent(self): + """ Returns the actual `wx.NotifyEvent`. """ + + return self.notify + + + def IsAllowed(self): + """ Returns whether the event is allowed or not. """ + + return self.notify.IsAllowed() + + + def Veto(self): + """ + Prevents the change announced by this event from happening. + + It is in general a good idea to notify the user about the reasons for + vetoing the change because otherwise the applications behaviour (which + just refuses to do what the user wants) might be quite surprising. + """ + + self.notify.Veto() + + + def Allow(self): + """ + This is the opposite of L{Veto}: it explicitly allows the event to be + processed. For most events it is not necessary to call this method as the + events are allowed anyhow but some are forbidden by default (this will + be mentioned in the corresponding event description). + """ + + self.notify.Allow() + + +# ---------------------------------------------------------------------- + +class ToolbarCommandCapture(wx.PyEvtHandler): + """ A class to handle the dropdown window menu. """ + + def __init__(self): + """ Default class constructor. """ + + wx.PyEvtHandler.__init__(self) + self._last_id = 0 + + + def GetCommandId(self): + """ Returns the event command identifier. """ + + return self._last_id + + + def ProcessEvent(self, event): + """ + Processes an event, searching event tables and calling zero or more suitable + event handler function(s). + + :param `event`: the event to process. + + :note: Normally, your application would not call this function: it is called + in the wxPython implementation to dispatch incoming user interface events + to the framework (and application). + However, you might need to call it if implementing new functionality (such as + a new control) where you define new event types, as opposed to allowing the + user to override functions. + + An instance where you might actually override the L{ProcessEvent} function is where + you want to direct event processing to event handlers not normally noticed by + wxPython. For example, in the document/view architecture, documents and views + are potential event handlers. When an event reaches a frame, L{ProcessEvent} will + need to be called on the associated document and view in case event handler + functions are associated with these objects. + + The normal order of event table searching is as follows: + + 1. If the object is disabled (via a call to `SetEvtHandlerEnabled`) the function + skips to step (6). + 2. If the object is a `wx.Window`, L{ProcessEvent} is recursively called on the window's + `wx.Validator`. If this returns ``True``, the function exits. + 3. wxWidgets `SearchEventTable` is called for this event handler. If this fails, the + base class table is tried, and so on until no more tables exist or an appropriate + function was found, in which case the function exits. + 4. The search is applied down the entire chain of event handlers (usually the chain + has a length of one). If this succeeds, the function exits. + 5. If the object is a `wx.Window` and the event is a `wx.CommandEvent`, L{ProcessEvent} is + recursively applied to the parent window's event handler. If this returns ``True``, + the function exits. + 6. Finally, L{ProcessEvent} is called on the `wx.App` object. + """ + + if event.GetEventType() == wx.wxEVT_COMMAND_MENU_SELECTED: + self._last_id = event.GetId() + return True + + if self.GetNextHandler(): + return self.GetNextHandler().ProcessEvent(event) + + return False + + +# ---------------------------------------------------------------------- + +class AuiToolBarItem(object): + """ + AuiToolBarItem is a toolbar element. + + It has a unique id (except for the separators which always have id = -1), the + style (telling whether it is a normal button, separator or a control), the + state (toggled or not, enabled or not) and short and long help strings. The + default implementations use the short help string for the tooltip text which + is popped up when the mouse pointer enters the tool and the long help string + for the applications status bar. + """ + + def __init__(self, item=None): + """ + Default class constructor. + + :param `item`: another instance of L{AuiToolBarItem}. + """ + + if item: + self.Assign(item) + return + + self.window = None + self.clockwisebmp = wx.NullBitmap + self.counterclockwisebmp = wx.NullBitmap + self.clockwisedisbmp = wx.NullBitmap + self.counterclockwisedisbmp = wx.NullBitmap + self.sizer_item = None + self.spacer_pixels = 0 + self.id = 0 + self.kind = ITEM_NORMAL + self.state = 0 # normal, enabled + self.proportion = 0 + self.active = True + self.dropdown = True + self.sticky = True + self.user_data = 0 + + self.label = "" + self.bitmap = wx.NullBitmap + self.disabled_bitmap = wx.NullBitmap + self.hover_bitmap = wx.NullBitmap + self.short_help = "" + self.long_help = "" + self.min_size = wx.Size(-1, -1) + self.alignment = wx.ALIGN_CENTER + self.orientation = AUI_TBTOOL_HORIZONTAL + + + def Assign(self, c): + """ + Assigns the properties of the L{AuiToolBarItem} `c` to `self`. + + :param `c`: another instance of L{AuiToolBarItem}. + """ + + self.window = c.window + self.label = c.label + self.bitmap = c.bitmap + self.disabled_bitmap = c.disabled_bitmap + self.hover_bitmap = c.hover_bitmap + self.short_help = c.short_help + self.long_help = c.long_help + self.sizer_item = c.sizer_item + self.min_size = c.min_size + self.spacer_pixels = c.spacer_pixels + self.id = c.id + self.kind = c.kind + self.state = c.state + self.proportion = c.proportion + self.active = c.active + self.dropdown = c.dropdown + self.sticky = c.sticky + self.user_data = c.user_data + self.alignment = c.alignment + self.orientation = c.orientation + + + def SetWindow(self, w): + """ + Assigns a window to the toolbar item. + + :param `w`: an instance of `wx.Window`. + """ + + self.window = w + + + def GetWindow(self): + """ Returns window associated to the toolbar item. """ + + return self.window + + + def SetId(self, new_id): + """ + Sets the toolbar item identifier. + + :param `new_id`: the new tool id. + """ + + self.id = new_id + + + def GetId(self): + """ Returns the toolbar item identifier. """ + + return self.id + + + def SetKind(self, new_kind): + """ + Sets the L{AuiToolBarItem} kind. + + :param `new_kind`: can be one of the following items: + + ======================== ============================= + Item Kind Description + ======================== ============================= + ``ITEM_CONTROL`` The item in the `AuiToolBar` is a control + ``ITEM_LABEL`` The item in the `AuiToolBar` is a text label + ``ITEM_SPACER`` The item in the `AuiToolBar` is a spacer + ``ITEM_SEPARATOR`` The item in the `AuiToolBar` is a separator + ``ITEM_CHECK`` The item in the `AuiToolBar` is a toolbar check item + ``ITEM_NORMAL`` The item in the `AuiToolBar` is a standard toolbar item + ``ITEM_RADIO`` The item in the `AuiToolBar` is a toolbar radio item + ======================== ============================= + """ + + self.kind = new_kind + + + def GetKind(self): + """ Returns the toolbar item kind. See L{SetKind} for more details. """ + + return self.kind + + + def SetState(self, new_state): + """ + Sets the toolbar item state. + + :param `new_state`: can be one of the following states: + + ============================================ ====================================== + Button State Constant Description + ============================================ ====================================== + ``AUI_BUTTON_STATE_NORMAL`` Normal button state + ``AUI_BUTTON_STATE_HOVER`` Hovered button state + ``AUI_BUTTON_STATE_PRESSED`` Pressed button state + ``AUI_BUTTON_STATE_DISABLED`` Disabled button state + ``AUI_BUTTON_STATE_HIDDEN`` Hidden button state + ``AUI_BUTTON_STATE_CHECKED`` Checked button state + ============================================ ====================================== + + """ + + self.state = new_state + + + def GetState(self): + """ + Returns the toolbar item state. See L{SetState} for more details. + + :see: L{SetState} + """ + + return self.state + + + def SetSizerItem(self, s): + """ + Associates a sizer item to this toolbar item. + + :param `s`: an instance of `wx.SizerItem`. + """ + + self.sizer_item = s + + + def GetSizerItem(self): + """ Returns the associated sizer item. """ + + return self.sizer_item + + + def SetLabel(self, s): + """ + Sets the toolbar item label. + + :param `s`: a string specifying the toolbar item label. + """ + + self.label = s + + + def GetLabel(self): + """ Returns the toolbar item label. """ + + return self.label + + + def SetBitmap(self, bmp): + """ + Sets the toolbar item bitmap. + + :param `bmp`: an instance of `wx.Bitmap`. + """ + + self.bitmap = bmp + + + def GetBitmap(self): + """ Returns the toolbar item bitmap. """ + + return self.GetRotatedBitmap(False) + + + def SetDisabledBitmap(self, bmp): + """ + Sets the toolbar item disabled bitmap. + + :param `bmp`: an instance of `wx.Bitmap`. + """ + + self.disabled_bitmap = bmp + + + def GetDisabledBitmap(self): + """ Returns the toolbar item disabled bitmap. """ + + return self.GetRotatedBitmap(True) + + + def SetHoverBitmap(self, bmp): + """ + Sets the toolbar item hover bitmap. + + :param `bmp`: an instance of `wx.Bitmap`. + """ + + self.hover_bitmap = bmp + + + def SetOrientation(self, a): + """ + Sets the toolbar tool orientation. + + :param `a`: one of ``AUI_TBTOOL_HORIZONTAL``, ``AUI_TBTOOL_VERT_CLOCKWISE`` or + ``AUI_TBTOOL_VERT_COUNTERCLOCKWISE``. + """ + + self.orientation = a + + + def GetOrientation(self): + """ Returns the toolbar tool orientation. """ + + return self.orientation + + + def GetHoverBitmap(self): + """ Returns the toolbar item hover bitmap. """ + + return self.hover_bitmap + + + def GetRotatedBitmap(self, disabled): + """ + Returns the correct bitmap depending on the tool orientation. + + :param `disabled`: whether to return the disabled bitmap or not. + """ + + bitmap_to_rotate = (disabled and [self.disabled_bitmap] or [self.bitmap])[0] + if not bitmap_to_rotate.IsOk() or self.orientation == AUI_TBTOOL_HORIZONTAL: + return bitmap_to_rotate + + rotated_bitmap = wx.NullBitmap + clockwise = True + if self.orientation == AUI_TBTOOL_VERT_CLOCKWISE: + rotated_bitmap = (disabled and [self.clockwisedisbmp] or [self.clockwisebmp])[0] + + elif self.orientation == AUI_TBTOOL_VERT_COUNTERCLOCKWISE: + rotated_bitmap = (disabled and [self.counterclockwisedisbmp] or [self.counterclockwisebmp])[0] + clockwise = False + + if not rotated_bitmap.IsOk(): + rotated_bitmap = wx.BitmapFromImage(bitmap_to_rotate.ConvertToImage().Rotate90(clockwise)) + + return rotated_bitmap + + + def SetShortHelp(self, s): + """ + Sets the short help string for the L{AuiToolBarItem}, to be displayed in a + `wx.ToolTip` when the mouse hover over the toolbar item. + + :param `s`: the tool short help string. + """ + + self.short_help = s + + + def GetShortHelp(self): + """ Returns the short help string for the L{AuiToolBarItem}. """ + + return self.short_help + + + def SetLongHelp(self, s): + """ + Sets the long help string for the toolbar item. This string is shown in the + statusbar (if any) of the parent frame when the mouse pointer is inside the + tool. + + :param `s`: the tool long help string. + """ + + self.long_help = s + + + def GetLongHelp(self): + """ Returns the long help string for the L{AuiToolBarItem}. """ + + return self.long_help + + + def SetMinSize(self, s): + """ + Sets the toolbar item minimum size. + + :param `s`: an instance of `wx.Size`. + """ + + self.min_size = wx.Size(*s) + + + def GetMinSize(self): + """ Returns the toolbar item minimum size. """ + + return self.min_size + + + def SetSpacerPixels(self, s): + """ + Sets the number of pixels for a toolbar item with kind = ``ITEM_SEPARATOR``. + + :param `s`: number of pixels. + """ + + self.spacer_pixels = s + + + def GetSpacerPixels(self): + """ Returns the number of pixels for a toolbar item with kind = ``ITEM_SEPARATOR``. """ + + return self.spacer_pixels + + + def SetProportion(self, p): + """ + Sets the L{AuiToolBarItem} proportion in the toolbar. + + :param `p`: the item proportion. + """ + + self.proportion = p + + + def GetProportion(self): + """ Returns the L{AuiToolBarItem} proportion in the toolbar. """ + + return self.proportion + + + def SetActive(self, b): + """ + Activates/deactivates the toolbar item. + + :param `b`: ``True`` to activate the item, ``False`` to deactivate it. + """ + + self.active = b + + + def IsActive(self): + """ Returns whether the toolbar item is active or not. """ + + return self.active + + + def SetHasDropDown(self, b): + """ + Sets whether the toolbar item has an associated dropdown menu. + + :param `b`: ``True`` to set a dropdown menu, ``False`` otherwise. + """ + + self.dropdown = b + + + def HasDropDown(self): + """ Returns whether the toolbar item has an associated dropdown menu or not. """ + + return self.dropdown + + + def SetSticky(self, b): + """ + Sets whether the toolbar item is sticky (permanent highlight after mouse enter) + or not. + + :param `b`: ``True`` to set the item as sticky, ``False`` otherwise. + """ + + self.sticky = b + + + def IsSticky(self): + """ Returns whether the toolbar item has a sticky behaviour or not. """ + + return self.sticky + + + def SetUserData(self, l): + """ + Associates some kind of user data to the toolbar item. + + :param `l`: a Python object. + + :note: The user data can be any Python object. + """ + + self.user_data = l + + + def GetUserData(self): + """ Returns the associated user data. """ + + return self.user_data + + + def SetAlignment(self, l): + """ + Sets the toolbar item alignment. + + :param `l`: the item alignment, which can be one of the available `wx.Sizer` + alignments. + """ + + self.alignment = l + + + def GetAlignment(self): + """ Returns the toolbar item alignment. """ + + return self.alignment + + +# ---------------------------------------------------------------------- + +class AuiDefaultToolBarArt(object): + """ + Toolbar art provider code - a tab provider provides all drawing functionality to + the L{AuiToolBar}. This allows the L{AuiToolBar} to have a plugable look-and-feel. + + By default, a L{AuiToolBar} uses an instance of this class called L{AuiDefaultToolBarArt} + which provides bitmap art and a colour scheme that is adapted to the major platforms' + look. You can either derive from that class to alter its behaviour or write a + completely new tab art class. Call L{AuiToolBar.SetArtProvider} to make use this + new tab art. + """ + + def __init__(self): + """ Default class constructor. """ + + self._base_colour = GetBaseColour() + + self._agwFlags = 0 + self._text_orientation = AUI_TBTOOL_TEXT_BOTTOM + self._highlight_colour = wx.SystemSettings.GetColour(wx.SYS_COLOUR_HIGHLIGHT) + + self._separator_size = 7 + self._orientation = AUI_TBTOOL_HORIZONTAL + self._gripper_size = 7 + self._overflow_size = 16 + + darker1_colour = StepColour(self._base_colour, 85) + darker2_colour = StepColour(self._base_colour, 75) + darker3_colour = StepColour(self._base_colour, 60) + darker4_colour = StepColour(self._base_colour, 50) + darker5_colour = StepColour(self._base_colour, 40) + + self._gripper_pen1 = wx.Pen(darker5_colour) + self._gripper_pen2 = wx.Pen(darker3_colour) + self._gripper_pen3 = wx.WHITE_PEN + + button_dropdown_bits = "\xe0\xf1\xfb" + overflow_bits = "\x80\xff\x80\xc1\xe3\xf7" + + self._button_dropdown_bmp = BitmapFromBits(button_dropdown_bits, 5, 3, wx.BLACK) + self._disabled_button_dropdown_bmp = BitmapFromBits(button_dropdown_bits, 5, 3, + wx.Colour(128, 128, 128)) + self._overflow_bmp = BitmapFromBits(overflow_bits, 7, 6, wx.BLACK) + self._disabled_overflow_bmp = BitmapFromBits(overflow_bits, 7, 6, wx.Colour(128, 128, 128)) + + self._font = wx.SystemSettings.GetFont(wx.SYS_DEFAULT_GUI_FONT) + + + def Clone(self): + """ Clones the L{AuiToolBar} art. """ + + return AuiDefaultToolBarArt() + + + def SetAGWFlags(self, agwFlags): + """ + Sets the toolbar art flags. + + :param `agwFlags`: a combination of the following values: + + ==================================== ================================== + Flag name Description + ==================================== ================================== + ``AUI_TB_TEXT`` Shows the text in the toolbar buttons; by default only icons are shown + ``AUI_TB_NO_TOOLTIPS`` Don't show tooltips on `AuiToolBar` items + ``AUI_TB_NO_AUTORESIZE`` Do not auto-resize the `AuiToolBar` + ``AUI_TB_GRIPPER`` Shows a gripper on the `AuiToolBar` + ``AUI_TB_OVERFLOW`` The `AuiToolBar` can contain overflow items + ``AUI_TB_VERTICAL`` The `AuiToolBar` is vertical + ``AUI_TB_HORZ_LAYOUT`` Shows the text and the icons alongside, not vertically stacked. This style must be used with ``AUI_TB_TEXT`` + ``AUI_TB_PLAIN_BACKGROUND`` Don't draw a gradient background on the toolbar + ``AUI_TB_HORZ_TEXT`` Combination of ``AUI_TB_HORZ_LAYOUT`` and ``AUI_TB_TEXT`` + ==================================== ================================== + + """ + + self._agwFlags = agwFlags + + + def GetAGWFlags(self): + """ + Returns the L{AuiDefaultToolBarArt} flags. See L{SetAGWFlags} for more + details. + + :see: L{SetAGWFlags} + """ + + return self._agwFlags + + + def SetFont(self, font): + """ + Sets the L{AuiDefaultToolBarArt} font. + + :param `font`: a `wx.Font` object. + """ + + self._font = font + + + def SetTextOrientation(self, orientation): + """ + Sets the text orientation. + + :param `orientation`: can be one of the following constants: + + ==================================== ================================== + Orientation Switches Description + ==================================== ================================== + ``AUI_TBTOOL_TEXT_LEFT`` Text in `AuiToolBar` items is aligned left + ``AUI_TBTOOL_TEXT_RIGHT`` Text in `AuiToolBar` items is aligned right + ``AUI_TBTOOL_TEXT_TOP`` Text in `AuiToolBar` items is aligned top + ``AUI_TBTOOL_TEXT_BOTTOM`` Text in `AuiToolBar` items is aligned bottom + ==================================== ================================== + + """ + + self._text_orientation = orientation + + + def GetFont(self): + """ Returns the L{AuiDefaultToolBarArt} font. """ + + return self._font + + + def GetTextOrientation(self): + """ + Returns the L{AuiDefaultToolBarArt} text orientation. See + L{SetTextOrientation} for more details. + + :see: L{SetTextOrientation} + """ + + return self._text_orientation + + + def SetOrientation(self, orientation): + """ + Sets the toolbar tool orientation. + + :param `orientation`: one of ``AUI_TBTOOL_HORIZONTAL``, ``AUI_TBTOOL_VERT_CLOCKWISE`` or + ``AUI_TBTOOL_VERT_COUNTERCLOCKWISE``. + """ + + self._orientation = orientation + + + def GetOrientation(self): + """ Returns the toolbar orientation. """ + + return self._orientation + + + def DrawBackground(self, dc, wnd, _rect, horizontal=True): + """ + Draws a toolbar background with a gradient shading. + + :param `dc`: a `wx.DC` device context; + :param `wnd`: a `wx.Window` derived window; + :param `_rect`: the L{AuiToolBar} rectangle; + :param `horizontal`: ``True`` if the toolbar is horizontal, ``False`` if it is vertical. + """ + + rect = wx.Rect(*_rect) + + start_colour = StepColour(self._base_colour, 180) + end_colour = StepColour(self._base_colour, 85) + reflex_colour = StepColour(self._base_colour, 95) + + dc.GradientFillLinear(rect, start_colour, end_colour, + (horizontal and [wx.SOUTH] or [wx.EAST])[0]) + + left = rect.GetLeft() + right = rect.GetRight() + top = rect.GetTop() + bottom = rect.GetBottom() + + dc.SetPen(wx.Pen(reflex_colour)) + if horizontal: + dc.DrawLine(left, bottom, right+1, bottom) + else: + dc.DrawLine(right, top, right, bottom+1) + + + def DrawPlainBackground(self, dc, wnd, _rect): + """ + Draws a toolbar background with a plain colour. + + This method contrasts with the default behaviour of the L{AuiToolBar} that + draws a background gradient and this break the window design when putting + it within a control that has margin between the borders and the toolbar + (example: put L{AuiToolBar} within a `wx.StaticBoxSizer` that has a plain background). + + :param `dc`: a `wx.DC` device context; + :param `wnd`: a `wx.Window` derived window; + :param `_rect`: the L{AuiToolBar} rectangle. + """ + + rect = wx.Rect(*_rect) + rect.height += 1 + + dc.SetBrush(wx.Brush(wx.SystemSettings.GetColour(wx.SYS_COLOUR_3DFACE))) + dc.DrawRectangle(rect.x - 1, rect.y - 1, rect.width + 2, rect.height + 1) + + + def DrawLabel(self, dc, wnd, item, rect): + """ + Draws a toolbar item label. + + :param `dc`: a `wx.DC` device context; + :param `wnd`: a `wx.Window` derived window; + :param `item`: an instance of L{AuiToolBarItem}; + :param `rect`: the L{AuiToolBarItem} rectangle. + """ + + dc.SetFont(self._font) + dc.SetTextForeground(wx.BLACK) + orient = item.GetOrientation() + + horizontal = orient == AUI_TBTOOL_HORIZONTAL + # we only care about the text height here since the text + # will get cropped based on the width of the item + label_size = GetLabelSize(dc, item.GetLabel(), not horizontal) + text_width = label_size.GetWidth() + text_height = label_size.GetHeight() + + if orient == AUI_TBTOOL_HORIZONTAL: + text_x = rect.x + text_y = rect.y + (rect.height-text_height)/2 + dc.DrawText(item.GetLabel(), text_x, text_y) + + elif orient == AUI_TBTOOL_VERT_CLOCKWISE: + text_x = rect.x + (rect.width+text_width)/2 + text_y = rect.y + dc.DrawRotatedText(item.GetLabel(), text_x, text_y, 270) + + elif AUI_TBTOOL_VERT_COUNTERCLOCKWISE: + text_x = rect.x + (rect.width-text_width)/2 + text_y = rect.y + text_height + dc.DrawRotatedText(item.GetLabel(), text_x, text_y, 90) + + + def DrawButton(self, dc, wnd, item, rect): + """ + Draws a toolbar item button. + + :param `dc`: a `wx.DC` device context; + :param `wnd`: a `wx.Window` derived window; + :param `item`: an instance of L{AuiToolBarItem}; + :param `rect`: the L{AuiToolBarItem} rectangle. + """ + + bmp_rect, text_rect = self.GetToolsPosition(dc, item, rect) + + if not item.GetState() & AUI_BUTTON_STATE_DISABLED: + + if item.GetState() & AUI_BUTTON_STATE_PRESSED: + + dc.SetPen(wx.Pen(self._highlight_colour)) + dc.SetBrush(wx.Brush(StepColour(self._highlight_colour, 150))) + dc.DrawRectangleRect(rect) + + elif item.GetState() & AUI_BUTTON_STATE_HOVER or item.IsSticky(): + + dc.SetPen(wx.Pen(self._highlight_colour)) + dc.SetBrush(wx.Brush(StepColour(self._highlight_colour, 170))) + + # draw an even lighter background for checked item hovers (since + # the hover background is the same colour as the check background) + if item.GetState() & AUI_BUTTON_STATE_CHECKED: + dc.SetBrush(wx.Brush(StepColour(self._highlight_colour, 180))) + + dc.DrawRectangleRect(rect) + + elif item.GetState() & AUI_BUTTON_STATE_CHECKED: + + # it's important to put this code in an else statment after the + # hover, otherwise hovers won't draw properly for checked items + dc.SetPen(wx.Pen(self._highlight_colour)) + dc.SetBrush(wx.Brush(StepColour(self._highlight_colour, 170))) + dc.DrawRectangleRect(rect) + + if item.GetState() & AUI_BUTTON_STATE_DISABLED: + bmp = item.GetDisabledBitmap() + else: + bmp = item.GetBitmap() + + if bmp.IsOk(): + dc.DrawBitmap(bmp, bmp_rect.x, bmp_rect.y, True) + + # set the item's text colour based on if it is disabled + dc.SetTextForeground(wx.BLACK) + if item.GetState() & AUI_BUTTON_STATE_DISABLED: + dc.SetTextForeground(DISABLED_TEXT_COLOUR) + + if self._agwFlags & AUI_TB_TEXT and item.GetLabel() != "": + self.DrawLabel(dc, wnd, item, text_rect) + + + def DrawDropDownButton(self, dc, wnd, item, rect): + """ + Draws a toolbar dropdown button. + + :param `dc`: a `wx.DC` device context; + :param `wnd`: a `wx.Window` derived window; + :param `item`: an instance of L{AuiToolBarItem}; + :param `rect`: the L{AuiToolBarItem} rectangle. + """ + + dropbmp_x = dropbmp_y = 0 + + button_rect = wx.Rect(rect.x, rect.y, rect.width-BUTTON_DROPDOWN_WIDTH, rect.height) + dropdown_rect = wx.Rect(rect.x+rect.width-BUTTON_DROPDOWN_WIDTH-1, rect.y, BUTTON_DROPDOWN_WIDTH+1, rect.height) + + horizontal = item.GetOrientation() == AUI_TBTOOL_HORIZONTAL + + if horizontal: + button_rect = wx.Rect(rect.x, rect.y, rect.width-BUTTON_DROPDOWN_WIDTH, rect.height) + dropdown_rect = wx.Rect(rect.x+rect.width-BUTTON_DROPDOWN_WIDTH-1, rect.y, BUTTON_DROPDOWN_WIDTH+1, rect.height) + else: + button_rect = wx.Rect(rect.x, rect.y, rect.width, rect.height-BUTTON_DROPDOWN_WIDTH) + dropdown_rect = wx.Rect(rect.x, rect.y+rect.height-BUTTON_DROPDOWN_WIDTH-1, rect.width, BUTTON_DROPDOWN_WIDTH+1) + + dropbmp_width = self._button_dropdown_bmp.GetWidth() + dropbmp_height = self._button_dropdown_bmp.GetHeight() + if not horizontal: + tmp = dropbmp_width + dropbmp_width = dropbmp_height + dropbmp_height = tmp + + dropbmp_x = dropdown_rect.x + (dropdown_rect.width/2) - dropbmp_width/2 + dropbmp_y = dropdown_rect.y + (dropdown_rect.height/2) - dropbmp_height/2 + + bmp_rect, text_rect = self.GetToolsPosition(dc, item, button_rect) + + if item.GetState() & AUI_BUTTON_STATE_PRESSED: + + dc.SetPen(wx.Pen(self._highlight_colour)) + dc.SetBrush(wx.Brush(StepColour(self._highlight_colour, 140))) + dc.DrawRectangleRect(button_rect) + dc.DrawRectangleRect(dropdown_rect) + + elif item.GetState() & AUI_BUTTON_STATE_HOVER or item.IsSticky(): + + dc.SetPen(wx.Pen(self._highlight_colour)) + dc.SetBrush(wx.Brush(StepColour(self._highlight_colour, 170))) + dc.DrawRectangleRect(button_rect) + dc.DrawRectangleRect(dropdown_rect) + + elif item.GetState() & AUI_BUTTON_STATE_CHECKED: + # it's important to put this code in an else statment after the + # hover, otherwise hovers won't draw properly for checked items + dc.SetPen(wx.Pen(self._highlight_colour)) + dc.SetBrush(wx.Brush(StepColour(self._highlight_colour, 170))) + dc.DrawRectangle(button_rect) + dc.DrawRectangle(dropdown_rect) + + if item.GetState() & AUI_BUTTON_STATE_DISABLED: + + bmp = item.GetDisabledBitmap() + dropbmp = self._disabled_button_dropdown_bmp + + else: + + bmp = item.GetBitmap() + dropbmp = self._button_dropdown_bmp + + if not bmp.IsOk(): + return + + dc.DrawBitmap(bmp, bmp_rect.x, bmp_rect.y, True) + if horizontal: + dc.DrawBitmap(dropbmp, dropbmp_x, dropbmp_y, True) + else: + dc.DrawBitmap(wx.BitmapFromImage(dropbmp.ConvertToImage().Rotate90(item.GetOrientation() == AUI_TBTOOL_VERT_CLOCKWISE)), + dropbmp_x, dropbmp_y, True) + + # set the item's text colour based on if it is disabled + dc.SetTextForeground(wx.BLACK) + if item.GetState() & AUI_BUTTON_STATE_DISABLED: + dc.SetTextForeground(DISABLED_TEXT_COLOUR) + + if self._agwFlags & AUI_TB_TEXT and item.GetLabel() != "": + self.DrawLabel(dc, wnd, item, text_rect) + + + def DrawControlLabel(self, dc, wnd, item, rect): + """ + Draws a label for a toolbar control. + + :param `dc`: a `wx.DC` device context; + :param `wnd`: a `wx.Window` derived window; + :param `item`: an instance of L{AuiToolBarItem}; + :param `rect`: the L{AuiToolBarItem} rectangle. + """ + + label_size = GetLabelSize(dc, item.GetLabel(), item.GetOrientation() != AUI_TBTOOL_HORIZONTAL) + text_height = label_size.GetHeight() + text_width = label_size.GetWidth() + + dc.SetFont(self._font) + + if self._agwFlags & AUI_TB_TEXT: + + tx, text_height = dc.GetTextExtent("ABCDHgj") + + text_width, ty = dc.GetTextExtent(item.GetLabel()) + + # don't draw the label if it is wider than the item width + if text_width > rect.width: + return + + # set the label's text colour + dc.SetTextForeground(wx.BLACK) + + text_x = rect.x + (rect.width/2) - (text_width/2) + 1 + text_y = rect.y + rect.height - text_height - 1 + + if self._agwFlags & AUI_TB_TEXT and item.GetLabel() != "": + dc.DrawText(item.GetLabel(), text_x, text_y) + + + def GetLabelSize(self, dc, wnd, item): + """ + Returns the label size for a toolbar item. + + :param `dc`: a `wx.DC` device context; + :param `wnd`: a `wx.Window` derived window; + :param `item`: an instance of L{AuiToolBarItem}. + """ + + dc.SetFont(self._font) + label_size = GetLabelSize(dc, item.GetLabel(), self._orientation != AUI_TBTOOL_HORIZONTAL) + + return wx.Size(item.GetMinSize().GetWidth(), label_size.GetHeight()) + + + def GetToolSize(self, dc, wnd, item): + """ + Returns the toolbar item size. + + :param `dc`: a `wx.DC` device context; + :param `wnd`: a `wx.Window` derived window; + :param `item`: an instance of L{AuiToolBarItem}. + """ + + if not item.GetBitmap().IsOk() and not self._agwFlags & AUI_TB_TEXT: + return wx.Size(16, 16) + + width = item.GetBitmap().GetWidth() + height = item.GetBitmap().GetHeight() + + if self._agwFlags & AUI_TB_TEXT: + + dc.SetFont(self._font) + label_size = GetLabelSize(dc, item.GetLabel(), self.GetOrientation() != AUI_TBTOOL_HORIZONTAL) + padding = 6 + + if self._text_orientation == AUI_TBTOOL_TEXT_BOTTOM: + + if self.GetOrientation() != AUI_TBTOOL_HORIZONTAL: + height += 3 # space between top border and bitmap + height += 3 # space between bitmap and text + padding = 0 + + height += label_size.GetHeight() + + if item.GetLabel() != "": + width = max(width, label_size.GetWidth()+padding) + + elif self._text_orientation == AUI_TBTOOL_TEXT_RIGHT and item.GetLabel() != "": + + if self.GetOrientation() == AUI_TBTOOL_HORIZONTAL: + + width += 3 # space between left border and bitmap + width += 3 # space between bitmap and text + padding = 0 + + width += label_size.GetWidth() + height = max(height, label_size.GetHeight()+padding) + + # if the tool has a dropdown button, add it to the width + if item.HasDropDown(): + if item.GetOrientation() == AUI_TBTOOL_HORIZONTAL: + width += BUTTON_DROPDOWN_WIDTH+4 + else: + height += BUTTON_DROPDOWN_WIDTH+4 + + return wx.Size(width, height) + + + def DrawSeparator(self, dc, wnd, _rect): + """ + Draws a toolbar separator. + + :param `dc`: a `wx.DC` device context; + :param `wnd`: a `wx.Window` derived window; + :param `_rect`: the L{AuiToolBarItem} rectangle. + """ + + horizontal = True + if self._agwFlags & AUI_TB_VERTICAL: + horizontal = False + + rect = wx.Rect(*_rect) + + if horizontal: + + rect.x += (rect.width/2) + rect.width = 1 + new_height = (rect.height*3)/4 + rect.y += (rect.height/2) - (new_height/2) + rect.height = new_height + + else: + + rect.y += (rect.height/2) + rect.height = 1 + new_width = (rect.width*3)/4 + rect.x += (rect.width/2) - (new_width/2) + rect.width = new_width + + start_colour = StepColour(self._base_colour, 80) + end_colour = StepColour(self._base_colour, 80) + dc.GradientFillLinear(rect, start_colour, end_colour, (horizontal and [wx.SOUTH] or [wx.EAST])[0]) + + + def DrawGripper(self, dc, wnd, rect): + """ + Draws the toolbar gripper. + + :param `dc`: a `wx.DC` device context; + :param `wnd`: a `wx.Window` derived window; + :param `rect`: the L{AuiToolBar} rectangle. + """ + + i = 0 + while 1: + + if self._agwFlags & AUI_TB_VERTICAL: + + x = rect.x + (i*4) + 4 + y = rect.y + 3 + if x > rect.GetWidth() - 4: + break + + else: + + x = rect.x + 3 + y = rect.y + (i*4) + 4 + if y > rect.GetHeight() - 4: + break + + dc.SetPen(self._gripper_pen1) + dc.DrawPoint(x, y) + dc.SetPen(self._gripper_pen2) + dc.DrawPoint(x, y+1) + dc.DrawPoint(x+1, y) + dc.SetPen(self._gripper_pen3) + dc.DrawPoint(x+2, y+1) + dc.DrawPoint(x+2, y+2) + dc.DrawPoint(x+1, y+2) + + i += 1 + + + def DrawOverflowButton(self, dc, wnd, rect, state): + """ + Draws the overflow button for the L{AuiToolBar}. + + :param `dc`: a `wx.DC` device context; + :param `wnd`: a `wx.Window` derived window; + :param `rect`: the L{AuiToolBar} rectangle; + :param `state`: the overflow button state. + """ + + if state & AUI_BUTTON_STATE_HOVER or state & AUI_BUTTON_STATE_PRESSED: + + cli_rect = wnd.GetClientRect() + light_gray_bg = StepColour(self._highlight_colour, 170) + + if self._agwFlags & AUI_TB_VERTICAL: + + dc.SetPen(wx.Pen(self._highlight_colour)) + dc.DrawLine(rect.x, rect.y, rect.x+rect.width, rect.y) + dc.SetPen(wx.Pen(light_gray_bg)) + dc.SetBrush(wx.Brush(light_gray_bg)) + dc.DrawRectangle(rect.x, rect.y+1, rect.width, rect.height) + + else: + + dc.SetPen(wx.Pen(self._highlight_colour)) + dc.DrawLine(rect.x, rect.y, rect.x, rect.y+rect.height) + dc.SetPen(wx.Pen(light_gray_bg)) + dc.SetBrush(wx.Brush(light_gray_bg)) + dc.DrawRectangle(rect.x+1, rect.y, rect.width, rect.height) + + x = rect.x + 1 + (rect.width-self._overflow_bmp.GetWidth())/2 + y = rect.y + 1 + (rect.height-self._overflow_bmp.GetHeight())/2 + dc.DrawBitmap(self._overflow_bmp, x, y, True) + + + def GetElementSize(self, element_id): + """ + Returns the size of a UI element in the L{AuiToolBar}. + + :param `element_id`: can be one of the following: + + ==================================== ================================== + Element Identifier Description + ==================================== ================================== + ``AUI_TBART_SEPARATOR_SIZE`` Separator size in `AuiToolBar` + ``AUI_TBART_GRIPPER_SIZE`` Gripper size in `AuiToolBar` + ``AUI_TBART_OVERFLOW_SIZE`` Overflow button size in `AuiToolBar` + ==================================== ================================== + """ + + if element_id == AUI_TBART_SEPARATOR_SIZE: + return self._separator_size + elif element_id == AUI_TBART_GRIPPER_SIZE: + return self._gripper_size + elif element_id == AUI_TBART_OVERFLOW_SIZE: + return self._overflow_size + + return 0 + + + def SetElementSize(self, element_id, size): + """ + Sets the size of a UI element in the L{AuiToolBar}. + + :param `element_id`: can be one of the following: + + ==================================== ================================== + Element Identifier Description + ==================================== ================================== + ``AUI_TBART_SEPARATOR_SIZE`` Separator size in `AuiToolBar` + ``AUI_TBART_GRIPPER_SIZE`` Gripper size in `AuiToolBar` + ``AUI_TBART_OVERFLOW_SIZE`` Overflow button size in `AuiToolBar` + ==================================== ================================== + + :param `size`: the new size of the UI element. + """ + + if element_id == AUI_TBART_SEPARATOR_SIZE: + self._separator_size = size + elif element_id == AUI_TBART_GRIPPER_SIZE: + self._gripper_size = size + elif element_id == AUI_TBART_OVERFLOW_SIZE: + self._overflow_size = size + + + def ShowDropDown(self, wnd, items): + """ + Shows the drop down window menu for overflow items. + + :param `wnd`: an instance of `wx.Window`; + :param `items`: the overflow toolbar items (a Python list). + """ + + menuPopup = wx.Menu() + items_added = 0 + + for item in items: + + if item.GetKind() not in [ITEM_SEPARATOR, ITEM_SPACER, ITEM_CONTROL]: + + text = item.GetShortHelp() + if text == "": + text = item.GetLabel() + if text == "": + text = " " + + kind = item.GetKind() + m = wx.MenuItem(menuPopup, item.GetId(), text, item.GetShortHelp(), kind) + orientation = item.GetOrientation() + item.SetOrientation(AUI_TBTOOL_HORIZONTAL) + + if kind not in [ITEM_CHECK, ITEM_RADIO]: + m.SetBitmap(item.GetBitmap()) + + item.SetOrientation(orientation) + + menuPopup.AppendItem(m) + if kind in [ITEM_CHECK, ITEM_RADIO]: + state = (item.state & AUI_BUTTON_STATE_CHECKED and [True] or [False])[0] + m.Check(state) + + items_added += 1 + + else: + + if items_added > 0 and item.GetKind() == ITEM_SEPARATOR: + menuPopup.AppendSeparator() + + # find out where to put the popup menu of window items + pt = wx.GetMousePosition() + pt = wnd.ScreenToClient(pt) + + # find out the screen coordinate at the bottom of the tab ctrl + cli_rect = wnd.GetClientRect() + pt.y = cli_rect.y + cli_rect.height + + cc = ToolbarCommandCapture() + wnd.PushEventHandler(cc) + + # Adjustments to get slightly better menu placement + if wx.Platform == "__WXMAC__": + pt.y += 5 + pt.x -= 5 + + wnd.PopupMenu(menuPopup, pt) + command = cc.GetCommandId() + wnd.PopEventHandler(True) + + return command + + + def GetToolsPosition(self, dc, item, rect): + """ + Returns the bitmap and text rectangles for a toolbar item. + + :param `dc`: a `wx.DC` device context; + :param `item`: an instance of L{AuiToolBarItem}; + :param `rect`: the tool rect. + """ + + text_width = text_height = 0 + horizontal = self._orientation == AUI_TBTOOL_HORIZONTAL + text_bottom = self._text_orientation == AUI_TBTOOL_TEXT_BOTTOM + text_right = self._text_orientation == AUI_TBTOOL_TEXT_RIGHT + bmp_width = item.GetBitmap().GetWidth() + bmp_height = item.GetBitmap().GetHeight() + + if self._agwFlags & AUI_TB_TEXT: + dc.SetFont(self._font) + label_size = GetLabelSize(dc, item.GetLabel(), not horizontal) + text_height = label_size.GetHeight() + text_width = label_size.GetWidth() + + bmp_x = bmp_y = text_x = text_y = 0 + + if horizontal and text_bottom: + bmp_x = rect.x + (rect.width/2) - (bmp_width/2) + bmp_y = rect.y + 3 + text_x = rect.x + (rect.width/2) - (text_width/2) + text_y = rect.y + ((bmp_y - rect.y) * 2) + bmp_height + + elif horizontal and text_right: + bmp_x = rect.x + 3 + bmp_y = rect.y + (rect.height/2) - (bmp_height / 2) + text_x = rect.x + ((bmp_x - rect.x) * 2) + bmp_width + text_y = rect.y + (rect.height/2) - (text_height/2) + + elif not horizontal and text_bottom: + bmp_x = rect.x + (rect.width / 2) - (bmp_width / 2) + bmp_y = rect.y + 3 + text_x = rect.x + (rect.width / 2) - (text_width / 2) + text_y = rect.y + ((bmp_y - rect.y) * 2) + bmp_height + + bmp_rect = wx.Rect(bmp_x, bmp_y, bmp_width, bmp_height) + text_rect = wx.Rect(text_x, text_y, text_width, text_height) + + return bmp_rect, text_rect + + +class AuiToolBar(wx.PyControl): + """ + AuiToolBar is a completely owner-drawn toolbar perfectly integrated with the + AUI layout system. This allows drag and drop of toolbars, docking/floating + behaviour and the possibility to define "overflow" items in the toolbar itself. + + The default theme that is used is L{AuiDefaultToolBarArt}, which provides a modern, + glossy look and feel. The theme can be changed by calling L{AuiToolBar.SetArtProvider}. + """ + + def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition, + size=wx.DefaultSize, style=0, agwStyle=AUI_TB_DEFAULT_STYLE): + """ + Default class constructor. + + :param `parent`: the L{AuiToolBar} parent; + :param `id`: an identifier for the control: a value of -1 is taken to mean a default; + :param `pos`: the control position. A value of (-1, -1) indicates a default position, + chosen by either the windowing system or wxPython, depending on platform; + :param `size`: the control size. A value of (-1, -1) indicates a default size, + chosen by either the windowing system or wxPython, depending on platform; + :param `style`: the control window style; + :param `agwStyle`: the AGW-specific window style. This can be a combination of the + following bits: + + ==================================== ================================== + Flag name Description + ==================================== ================================== + ``AUI_TB_TEXT`` Shows the text in the toolbar buttons; by default only icons are shown + ``AUI_TB_NO_TOOLTIPS`` Don't show tooltips on `AuiToolBar` items + ``AUI_TB_NO_AUTORESIZE`` Do not auto-resize the `AuiToolBar` + ``AUI_TB_GRIPPER`` Shows a gripper on the `AuiToolBar` + ``AUI_TB_OVERFLOW`` The `AuiToolBar` can contain overflow items + ``AUI_TB_VERTICAL`` The `AuiToolBar` is vertical + ``AUI_TB_HORZ_LAYOUT`` Shows the text and the icons alongside, not vertically stacked. This style must be used with ``AUI_TB_TEXT`` + ``AUI_TB_PLAIN_BACKGROUND`` Don't draw a gradient background on the toolbar + ``AUI_TB_HORZ_TEXT`` Combination of ``AUI_TB_HORZ_LAYOUT`` and ``AUI_TB_TEXT`` + ==================================== ================================== + + The default value for `agwStyle` is: ``AUI_TB_DEFAULT_STYLE`` = 0 + + """ + + wx.PyControl.__init__(self, parent, id, pos, size, style|wx.BORDER_NONE) + + self._sizer = wx.BoxSizer(wx.HORIZONTAL) + self.SetSizer(self._sizer) + self._button_width = -1 + self._button_height = -1 + self._sizer_element_count = 0 + self._action_pos = wx.Point(-1, -1) + self._action_item = None + self._tip_item = None + self._art = AuiDefaultToolBarArt() + self._tool_packing = 2 + self._tool_border_padding = 3 + self._tool_text_orientation = AUI_TBTOOL_TEXT_BOTTOM + self._tool_orientation = AUI_TBTOOL_HORIZONTAL + self._tool_alignment = wx.EXPAND + self._gripper_sizer_item = None + self._overflow_sizer_item = None + self._dragging = False + + self._agwStyle = self._originalStyle = agwStyle + + self._gripper_visible = (self._agwStyle & AUI_TB_GRIPPER and [True] or [False])[0] + self._overflow_visible = (self._agwStyle & AUI_TB_OVERFLOW and [True] or [False])[0] + self._overflow_state = 0 + self._custom_overflow_prepend = [] + self._custom_overflow_append = [] + + self._items = [] + + self.SetMargins(5, 5, 2, 2) + self.SetFont(wx.NORMAL_FONT) + self._art.SetAGWFlags(self._agwStyle) + self.SetExtraStyle(wx.WS_EX_PROCESS_IDLE) + + if agwStyle & AUI_TB_HORZ_LAYOUT: + self.SetToolTextOrientation(AUI_TBTOOL_TEXT_RIGHT) + elif agwStyle & AUI_TB_VERTICAL: + if agwStyle & AUI_TB_CLOCKWISE: + self.SetToolOrientation(AUI_TBTOOL_VERT_CLOCKWISE) + elif agwStyle & AUI_TB_COUNTERCLOCKWISE: + self.SetToolOrientation(AUI_TBTOOL_VERT_COUNTERCLOCKWISE) + + self.SetBackgroundStyle(wx.BG_STYLE_CUSTOM) + + self.Bind(wx.EVT_SIZE, self.OnSize) + self.Bind(wx.EVT_IDLE, self.OnIdle) + self.Bind(wx.EVT_ERASE_BACKGROUND, self.OnEraseBackground) + self.Bind(wx.EVT_PAINT, self.OnPaint) + self.Bind(wx.EVT_LEFT_DOWN, self.OnLeftDown) + self.Bind(wx.EVT_LEFT_DCLICK, self.OnLeftDown) + self.Bind(wx.EVT_LEFT_UP, self.OnLeftUp) + self.Bind(wx.EVT_RIGHT_DOWN, self.OnRightDown) + self.Bind(wx.EVT_RIGHT_DCLICK, self.OnRightDown) + self.Bind(wx.EVT_RIGHT_UP, self.OnRightUp) + self.Bind(wx.EVT_MIDDLE_DOWN, self.OnMiddleDown) + self.Bind(wx.EVT_MIDDLE_DCLICK, self.OnMiddleDown) + self.Bind(wx.EVT_MIDDLE_UP, self.OnMiddleUp) + self.Bind(wx.EVT_MOTION, self.OnMotion) + self.Bind(wx.EVT_LEAVE_WINDOW, self.OnLeaveWindow) + self.Bind(wx.EVT_SET_CURSOR, self.OnSetCursor) + + + def SetWindowStyleFlag(self, style): + """ + Sets the style of the window. + + :param `style`: the new window style. + + :note: Please note that some styles cannot be changed after the window + creation and that `Refresh` might need to be be called after changing the + others for the change to take place immediately. + + :note: Overridden from `wx.PyControl`. + """ + + wx.PyControl.SetWindowStyleFlag(self, style|wx.BORDER_NONE) + + + def SetAGWWindowStyleFlag(self, agwStyle): + """ + Sets the AGW-specific style of the window. + + :param `agwStyle`: the new window style. This can be a combination of the + following bits: + + ==================================== ================================== + Flag name Description + ==================================== ================================== + ``AUI_TB_TEXT`` Shows the text in the toolbar buttons; by default only icons are shown + ``AUI_TB_NO_TOOLTIPS`` Don't show tooltips on `AuiToolBar` items + ``AUI_TB_NO_AUTORESIZE`` Do not auto-resize the `AuiToolBar` + ``AUI_TB_GRIPPER`` Shows a gripper on the `AuiToolBar` + ``AUI_TB_OVERFLOW`` The `AuiToolBar` can contain overflow items + ``AUI_TB_VERTICAL`` The `AuiToolBar` is vertical + ``AUI_TB_HORZ_LAYOUT`` Shows the text and the icons alongside, not vertically stacked. This style must be used with ``AUI_TB_TEXT`` + ``AUI_TB_PLAIN_BACKGROUND`` Don't draw a gradient background on the toolbar + ``AUI_TB_HORZ_TEXT`` Combination of ``AUI_TB_HORZ_LAYOUT`` and ``AUI_TB_TEXT`` + ==================================== ================================== + + :note: Please note that some styles cannot be changed after the window + creation and that `Refresh` might need to be be called after changing the + others for the change to take place immediately. + """ + + self._agwStyle = self._originalStyle = agwStyle + + if self._art: + self._art.SetAGWFlags(self._agwStyle) + + if agwStyle & AUI_TB_GRIPPER: + self._gripper_visible = True + else: + self._gripper_visible = False + + if agwStyle & AUI_TB_OVERFLOW: + self._overflow_visible = True + else: + self._overflow_visible = False + + if agwStyle & AUI_TB_HORZ_LAYOUT: + self.SetToolTextOrientation(AUI_TBTOOL_TEXT_RIGHT) + else: + self.SetToolTextOrientation(AUI_TBTOOL_TEXT_BOTTOM) + + if agwStyle & AUI_TB_VERTICAL: + if agwStyle & AUI_TB_CLOCKWISE: + self.SetToolOrientation(AUI_TBTOOL_VERT_CLOCKWISE) + elif agwStyle & AUI_TB_COUNTERCLOCKWISE: + self.SetToolOrientation(AUI_TBTOOL_VERT_COUNTERCLOCKWISE) + + + def GetAGWWindowStyleFlag(self): + """ + Returns the AGW-specific window style flag. + + :see: L{SetAGWWindowStyleFlag} for an explanation of various AGW-specific style. + """ + + return self._agwStyle + + + def SetArtProvider(self, art): + """ + Instructs L{AuiToolBar} to use art provider specified by parameter `art` + for all drawing calls. This allows plugable look-and-feel features. + + :param `art`: an art provider. + + :note: The previous art provider object, if any, will be deleted by L{AuiToolBar}. + """ + + del self._art + self._art = art + + if self._art: + self._art.SetAGWFlags(self._agwStyle) + self._art.SetTextOrientation(self._tool_text_orientation) + self._art.SetOrientation(self._tool_orientation) + + + def GetArtProvider(self): + """ Returns the current art provider being used. """ + + return self._art + + + def AddSimpleTool(self, tool_id, label, bitmap, short_help_string="", kind=ITEM_NORMAL): + """ + Adds a tool to the toolbar. This is the simplest method you can use to + ass an item to the L{AuiToolBar}. + + :param `tool_id`: an integer by which the tool may be identified in subsequent operations; + :param `label`: the toolbar tool label; + :param `bitmap`: the primary tool bitmap; + :param `short_help_string`: this string is used for the tools tooltip; + :param `kind`: the item kind. Can be one of the following: + + ======================== ============================= + Item Kind Description + ======================== ============================= + ``ITEM_CONTROL`` The item in the `AuiToolBar` is a control + ``ITEM_LABEL`` The item in the `AuiToolBar` is a text label + ``ITEM_SPACER`` The item in the `AuiToolBar` is a spacer + ``ITEM_SEPARATOR`` The item in the `AuiToolBar` is a separator + ``ITEM_CHECK`` The item in the `AuiToolBar` is a toolbar check item + ``ITEM_NORMAL`` The item in the `AuiToolBar` is a standard toolbar item + ``ITEM_RADIO`` The item in the `AuiToolBar` is a toolbar radio item + ======================== ============================= + """ + + return self.AddTool(tool_id, label, bitmap, wx.NullBitmap, kind, short_help_string, "", None) + + + def AddToggleTool(self, tool_id, bitmap, disabled_bitmap, toggle=False, client_data=None, short_help_string="", long_help_string=""): + """ + Adds a toggle tool to the toolbar. + + :param `tool_id`: an integer by which the tool may be identified in subsequent operations; + :param `bitmap`: the primary tool bitmap; + :param `disabled_bitmap`: the bitmap to use when the tool is disabled. If it is equal to + `wx.NullBitmap`, the disabled bitmap is automatically generated by greing the normal one; + :param `client_data`: whatever Python object to associate with the toolbar item; + :param `short_help_string`: this string is used for the tools tooltip; + :param `long_help_string`: this string is shown in the statusbar (if any) of the parent + frame when the mouse pointer is inside the tool. + """ + + kind = (toggle and [ITEM_CHECK] or [ITEM_NORMAL])[0] + return self.AddTool(tool_id, "", bitmap, disabled_bitmap, kind, short_help_string, long_help_string, client_data) + + + def AddTool(self, tool_id, label, bitmap, disabled_bitmap, kind, short_help_string, long_help_string, client_data): + """ + Adds a tool to the toolbar. This is the full feature version of L{AddTool}. + + :param `tool_id`: an integer by which the tool may be identified in subsequent operations; + :param `label`: the toolbar tool label; + :param `bitmap`: the primary tool bitmap; + :param `disabled_bitmap`: the bitmap to use when the tool is disabled. If it is equal to + `wx.NullBitmap`, the disabled bitmap is automatically generated by greing the normal one; + :param `kind`: the item kind. Can be one of the following: + + ======================== ============================= + Item Kind Description + ======================== ============================= + ``ITEM_CONTROL`` The item in the `AuiToolBar` is a control + ``ITEM_LABEL`` The item in the `AuiToolBar` is a text label + ``ITEM_SPACER`` The item in the `AuiToolBar` is a spacer + ``ITEM_SEPARATOR`` The item in the `AuiToolBar` is a separator + ``ITEM_CHECK`` The item in the `AuiToolBar` is a toolbar check item + ``ITEM_NORMAL`` The item in the `AuiToolBar` is a standard toolbar item + ``ITEM_RADIO`` The item in the `AuiToolBar` is a toolbar radio item + ======================== ============================= + + :param `short_help_string`: this string is used for the tools tooltip; + :param `long_help_string`: this string is shown in the statusbar (if any) of the parent + frame when the mouse pointer is inside the tool. + :param `client_data`: whatever Python object to associate with the toolbar item. + """ + + item = AuiToolBarItem() + item.window = None + item.label = label + item.bitmap = bitmap + item.disabled_bitmap = disabled_bitmap + item.short_help = short_help_string + item.long_help = long_help_string + item.active = True + item.dropdown = False + item.spacer_pixels = 0 + + if tool_id == wx.ID_ANY: + tool_id = wx.NewId() + + item.id = tool_id + item.state = 0 + item.proportion = 0 + item.kind = kind + item.sizer_item = None + item.min_size = wx.Size(-1, -1) + item.user_data = 0 + item.sticky = False + item.orientation = self._tool_orientation + + if not item.disabled_bitmap.IsOk(): + # no disabled bitmap specified, we need to make one + if item.bitmap.IsOk(): + item.disabled_bitmap = MakeDisabledBitmap(item.bitmap) + + self._items.append(item) + return self._items[-1] + + + def AddCheckTool(self, tool_id, label, bitmap, disabled_bitmap, short_help_string="", long_help_string="", client_data=None): + """ + Adds a new check (or toggle) tool to the L{AuiToolBar}. + + :see: L{AddTool}. + """ + + return self.AddTool(tool_id, label, bitmap, disabled_bitmap, ITEM_CHECK, short_help_string, long_help_string, client_data) + + + def AddRadioTool(self, tool_id, label, bitmap, disabled_bitmap, short_help_string="", long_help_string="", client_data=None): + """ + Adds a new radio tool to the toolbar. + + Consecutive radio tools form a radio group such that exactly one button + in the group is pressed at any moment, in other words whenever a button + in the group is pressed the previously pressed button is automatically + released. You should avoid having the radio groups of only one element + as it would be impossible for the user to use such button. + + :note: By default, the first button in the radio group is initially pressed, + the others are not. + + :see: L{AddTool}. + """ + + return self.AddTool(tool_id, label, bitmap, disabled_bitmap, ITEM_RADIO, short_help_string, long_help_string, client_data) + + + def AddControl(self, control, label=""): + """ + Adds any control to the toolbar, typically e.g. a combobox. + + :param `control`: the control to be added; + :param `label`: the label which appears if the control goes into the + overflow items in the toolbar. + """ + + item = AuiToolBarItem() + item.window = control + item.label = label + item.bitmap = wx.NullBitmap + item.disabled_bitmap = wx.NullBitmap + item.active = True + item.dropdown = False + item.spacer_pixels = 0 + item.id = control.GetId() + item.state = 0 + item.proportion = 0 + item.kind = ITEM_CONTROL + item.sizer_item = None + item.min_size = control.GetEffectiveMinSize() + item.user_data = 0 + item.sticky = False + item.orientation = self._tool_orientation + + self._items.append(item) + return self._items[-1] + + + def AddLabel(self, tool_id, label="", width=0): + """ + Adds a label tool to the L{AuiToolBar}. + + :param `tool_id`: an integer by which the tool may be identified in subsequent operations; + :param `label`: the toolbar tool label; + :param `width`: the tool width. + """ + + min_size = wx.Size(-1, -1) + + if width != -1: + min_size.x = width + + item = AuiToolBarItem() + item.window = None + item.label = label + item.bitmap = wx.NullBitmap + item.disabled_bitmap = wx.NullBitmap + item.active = True + item.dropdown = False + item.spacer_pixels = 0 + + if tool_id == wx.ID_ANY: + tool_id = wx.NewId() + + item.id = tool_id + item.state = 0 + item.proportion = 0 + item.kind = ITEM_LABEL + item.sizer_item = None + item.min_size = min_size + item.user_data = 0 + item.sticky = False + item.orientation = self._tool_orientation + + self._items.append(item) + return self._items[-1] + + + def AddSeparator(self): + """ Adds a separator for spacing groups of tools. """ + + item = AuiToolBarItem() + item.window = None + item.label = "" + item.bitmap = wx.NullBitmap + item.disabled_bitmap = wx.NullBitmap + item.active = True + item.dropdown = False + item.id = -1 + item.state = 0 + item.proportion = 0 + item.kind = ITEM_SEPARATOR + item.sizer_item = None + item.min_size = wx.Size(-1, -1) + item.user_data = 0 + item.sticky = False + item.orientation = self._tool_orientation + + self._items.append(item) + return self._items[-1] + + + def AddSpacer(self, pixels): + """ + Adds a spacer for spacing groups of tools. + + :param `pixels`: the width of the spacer. + """ + + item = AuiToolBarItem() + item.window = None + item.label = "" + item.bitmap = wx.NullBitmap + item.disabled_bitmap = wx.NullBitmap + item.active = True + item.dropdown = False + item.spacer_pixels = pixels + item.id = -1 + item.state = 0 + item.proportion = 0 + item.kind = ITEM_SPACER + item.sizer_item = None + item.min_size = wx.Size(-1, -1) + item.user_data = 0 + item.sticky = False + item.orientation = self._tool_orientation + + self._items.append(item) + return self._items[-1] + + + def AddStretchSpacer(self, proportion=1): + """ + Adds a stretchable spacer for spacing groups of tools. + + :param `proportion`: the stretchable spacer proportion. + """ + + item = AuiToolBarItem() + item.window = None + item.label = "" + item.bitmap = wx.NullBitmap + item.disabled_bitmap = wx.NullBitmap + item.active = True + item.dropdown = False + item.spacer_pixels = 0 + item.id = -1 + item.state = 0 + item.proportion = proportion + item.kind = ITEM_SPACER + item.sizer_item = None + item.min_size = wx.Size(-1, -1) + item.user_data = 0 + item.sticky = False + item.orientation = self._tool_orientation + + self._items.append(item) + return self._items[-1] + + + def Clear(self): + """ Deletes all the tools in the L{AuiToolBar}. """ + + self._items = [] + self._sizer_element_count = 0 + + + def ClearTools(self): + """ Deletes all the tools in the L{AuiToolBar}. """ + + self.Clear() + + + def DeleteTool(self, tool_id): + """ + Removes the specified tool from the toolbar and deletes it. + + :param `tool_id`: the L{AuiToolBarItem} identifier. + + :returns: ``True`` if the tool was deleted, ``False`` otherwise. + + :note: Note that it is unnecessary to call L{Realize} for the change to + take place, it will happen immediately. + """ + + idx = self.GetToolIndex(tool_id) + + if idx >= 0 and idx < len(self._items): + self._items.pop(idx) + self.Realize() + return True + + return False + + + def DeleteToolByPos(self, pos): + """ + This function behaves like L{DeleteTool} but it deletes the tool at the + specified position and not the one with the given id. + + :param `pos`: the tool position. + + :see: L{DeleteTool} + """ + + if pos >= 0 and pos < len(self._items): + + self._items.pop(pos) + self.Realize() + return True + + return False + + + def FindControl(self, id): + """ + Returns a pointer to the control identified by `id` or ``None`` if no corresponding + control is found. + + :param `id`: the control identifier. + """ + + wnd = self.FindWindow(id) + return wnd + + + def FindTool(self, tool_id): + """ + Finds a tool for the given tool id. + + :param `tool_id`: the L{AuiToolBarItem} identifier. + """ + + for item in self._items: + if item.id == tool_id: + return item + + return None + + + def FindToolForPosition(self, x, y): + """ + Finds a tool for the given mouse position. + + :param `x`: mouse `x` position; + :param `y`: mouse `y` position. + + :returns: a pointer to a L{AuiToolBarItem} if a tool is found, or ``None`` otherwise. + """ + + for i, item in enumerate(self._items): + if not item.sizer_item: + continue + + rect = item.sizer_item.GetRect() + if rect.Contains((x,y)): + + # if the item doesn't fit on the toolbar, return None + if not self.GetToolFitsByIndex(i): + return None + + return item + + return None + + + def FindToolForPositionWithPacking(self, x, y): + """ + Finds a tool for the given mouse position, taking into account also the + tool packing. + + :param `x`: mouse `x` position; + :param `y`: mouse `y` position. + + :returns: a pointer to a L{AuiToolBarItem} if a tool is found, or ``None`` otherwise. + """ + + count = len(self._items) + + for i, item in enumerate(self._items): + if not item.sizer_item: + continue + + rect = item.sizer_item.GetRect() + + # apply tool packing + if i+1 < count: + rect.width += self._tool_packing + + if rect.Contains((x,y)): + + # if the item doesn't fit on the toolbar, return None + if not self.GetToolFitsByIndex(i): + return None + + return item + + return None + + + def FindToolByIndex(self, pos): + """ + Finds a tool for the given tool position in the L{AuiToolBar}. + + :param `pos`: the tool position in the toolbar. + + :returns: a pointer to a L{AuiToolBarItem} if a tool is found, or ``None`` otherwise. + """ + + if pos < 0 or pos >= len(self._items): + return None + + return self._items[pos] + + + def SetToolBitmapSize(self, size): + """ + Sets the default size of each tool bitmap. The default bitmap size is + 16 by 15 pixels. + + :param `size`: the size of the bitmaps in the toolbar. + + :note: This should be called to tell the toolbar what the tool bitmap + size is. Call it before you add tools. + + :note: Note that this is the size of the bitmap you pass to L{AddTool}, + and not the eventual size of the tool button. + + :todo: Add `wx.ToolBar` compatibility, actually implementing this method. + """ + + # TODO: wx.ToolBar compatibility + pass + + + def GetToolBitmapSize(self): + """ + Returns the size of bitmap that the toolbar expects to have. The default + bitmap size is 16 by 15 pixels. + + :note: Note that this is the size of the bitmap you pass to L{AddTool}, + and not the eventual size of the tool button. + + :todo: Add `wx.ToolBar` compatibility, actually implementing this method. + """ + + # TODO: wx.ToolBar compatibility + return wx.Size(16, 15) + + + def SetToolProportion(self, tool_id, proportion): + """ + Sets the tool proportion in the toolbar. + + :param `tool_id`: the L{AuiToolBarItem} identifier; + :param `proportion`: the tool proportion in the toolbar. + """ + + item = self.FindTool(tool_id) + if not item: + return + + item.proportion = proportion + + + def GetToolProportion(self, tool_id): + """ + Returns the tool proportion in the toolbar. + + :param `tool_id`: the L{AuiToolBarItem} identifier. + """ + + item = self.FindTool(tool_id) + if not item: + return + + return item.proportion + + + def SetToolSeparation(self, separation): + """ + Sets the separator size for the toolbar. + + :param `separation`: the separator size in pixels. + """ + + if self._art: + self._art.SetElementSize(AUI_TBART_SEPARATOR_SIZE, separation) + + + def GetToolSeparation(self): + """ Returns the separator size for the toolbar, in pixels. """ + + if self._art: + return self._art.GetElementSize(AUI_TBART_SEPARATOR_SIZE) + + return 5 + + + def SetToolDropDown(self, tool_id, dropdown): + """ + Assigns a drop down window menu to the toolbar item. + + :param `tool_id`: the L{AuiToolBarItem} identifier; + :param `dropdown`: whether to assign a drop down menu or not. + """ + + item = self.FindTool(tool_id) + if not item: + return + + item.dropdown = dropdown + + + def GetToolDropDown(self, tool_id): + """ + Returns whether the toolbar item identified by `tool_id` has an associated + drop down window menu or not. + + :param `tool_id`: the L{AuiToolBarItem} identifier. + """ + + item = self.FindTool(tool_id) + if not item: + return + + return item.dropdown + + + def SetToolSticky(self, tool_id, sticky): + """ + Sets the toolbar item as sticky or non-sticky. + + :param `tool_id`: the L{AuiToolBarItem} identifier; + :param `sticky`: whether the tool should be sticky or not. + """ + + # ignore separators + if tool_id == -1: + return + + item = self.FindTool(tool_id) + if not item: + return + + if item.sticky == sticky: + return + + item.sticky = sticky + + self.Refresh(False) + self.Update() + + + def GetToolSticky(self, tool_id): + """ + Returns whether the toolbar item identified by `tool_id` has a sticky + behaviour or not. + + :param `tool_id`: the L{AuiToolBarItem} identifier. + """ + + item = self.FindTool(tool_id) + if not item: + return + + return item.sticky + + + def SetToolBorderPadding(self, padding): + """ + Sets the padding between the tool border and the label. + + :param `padding`: the padding in pixels. + """ + + self._tool_border_padding = padding + + + def GetToolBorderPadding(self): + """ Returns the padding between the tool border and the label, in pixels. """ + + return self._tool_border_padding + + + def SetToolTextOrientation(self, orientation): + """ + Sets the label orientation for the toolbar items. + + :param `orientation`: the L{AuiToolBarItem} label orientation. + """ + + self._tool_text_orientation = orientation + + if self._art: + self._art.SetTextOrientation(orientation) + + + def GetToolTextOrientation(self): + """ Returns the label orientation for the toolbar items. """ + + return self._tool_text_orientation + + + def SetToolOrientation(self, orientation): + """ + Sets the tool orientation for the toolbar items. + + :param `orientation`: the L{AuiToolBarItem} orientation. + """ + + self._tool_orientation = orientation + if self._art: + self._art.SetOrientation(orientation) + + + def GetToolOrientation(self): + """ Returns the orientation for the toolbar items. """ + + return self._tool_orientation + + + def SetToolPacking(self, packing): + """ + Sets the value used for spacing tools. The default value is 1 pixel. + + :param `packing`: the value for packing. + """ + + self._tool_packing = packing + + + def GetToolPacking(self): + """ Returns the value used for spacing tools. The default value is 1 pixel. """ + + return self._tool_packing + + + def SetOrientation(self, orientation): + """ + Sets the toolbar orientation. + + :param `orientation`: either ``wx.VERTICAL`` or ``wx.HORIZONTAL``. + + :note: This can be temporarily overridden by L{AuiManager} when floating and + docking a L{AuiToolBar}. + """ + + pass + + + def SetMargins(self, left=-1, right=-1, top=-1, bottom=-1): + """ + Set the values to be used as margins for the toolbar. + + :param `left`: the left toolbar margin; + :param `right`: the right toolbar margin; + :param `top`: the top toolbar margin; + :param `bottom`: the bottom toolbar margin. + """ + + if left != -1: + self._left_padding = left + if right != -1: + self._right_padding = right + if top != -1: + self._top_padding = top + if bottom != -1: + self._bottom_padding = bottom + + + def SetMarginsSize(self, size): + """ + Set the values to be used as margins for the toolbar. + + :param `size`: the margin size (an instance of `wx.Size`). + """ + + self.SetMargins(size.x, size.x, size.y, size.y) + + + def SetMarginsXY(self, x, y): + """ + Set the values to be used as margins for the toolbar. + + :param `x`: left margin, right margin and inter-tool separation value; + :param `y`: top margin, bottom margin and inter-tool separation value. + """ + + self.SetMargins(x, x, y, y) + + + def GetGripperVisible(self): + """ Returns whether the toolbar gripper is visible or not. """ + + return self._gripper_visible + + + def SetGripperVisible(self, visible): + """ + Sets whether the toolbar gripper is visible or not. + + :param `visible`: ``True`` for a visible gripper, ``False`` otherwise. + """ + + self._gripper_visible = visible + if visible: + self._agwStyle |= AUI_TB_GRIPPER + else: + self._agwStyle &= ~AUI_TB_GRIPPER + + self.Realize() + self.Refresh(False) + + + def GetOverflowVisible(self): + """ Returns whether the overflow button is visible or not. """ + + return self._overflow_visible + + + def SetOverflowVisible(self, visible): + """ + Sets whether the overflow button is visible or not. + + :param `visible`: ``True`` for a visible overflow button, ``False`` otherwise. + """ + + self._overflow_visible = visible + if visible: + self._agwStyle |= AUI_TB_OVERFLOW + else: + self._agwStyle &= ~AUI_TB_OVERFLOW + + self.Refresh(False) + + + def SetFont(self, font): + """ + Sets the L{AuiToolBar} font. + + :param `font`: a `wx.Font` object. + + :note: Overridden from `wx.PyControl`. + """ + + res = wx.PyControl.SetFont(self, font) + + if self._art: + self._art.SetFont(font) + + return res + + + def SetHoverItem(self, pitem): + """ + Sets a toolbar item to be currently hovered by the mouse. + + :param `pitem`: an instance of L{AuiToolBarItem}. + """ + + former_hover = None + + for item in self._items: + + if item.state & AUI_BUTTON_STATE_HOVER: + former_hover = item + + item.state &= ~AUI_BUTTON_STATE_HOVER + + if pitem: + pitem.state |= AUI_BUTTON_STATE_HOVER + + if former_hover != pitem: + self.Refresh(False) + self.Update() + + + def SetPressedItem(self, pitem): + """ + Sets a toolbar item to be currently in a "pressed" state. + + :param `pitem`: an instance of L{AuiToolBarItem}. + """ + + former_item = None + + for item in self._items: + + if item.state & AUI_BUTTON_STATE_PRESSED: + former_item = item + + item.state &= ~AUI_BUTTON_STATE_PRESSED + + if pitem: + pitem.state &= ~AUI_BUTTON_STATE_HOVER + pitem.state |= AUI_BUTTON_STATE_PRESSED + + if former_item != pitem: + self.Refresh(False) + self.Update() + + + def RefreshOverflowState(self): + """ Refreshes the overflow button. """ + + if not self._overflow_sizer_item: + self._overflow_state = 0 + return + + overflow_state = 0 + overflow_rect = self.GetOverflowRect() + + # find out the mouse's current position + pt = wx.GetMousePosition() + pt = self.ScreenToClient(pt) + + # find out if the mouse cursor is inside the dropdown rectangle + if overflow_rect.Contains((pt.x, pt.y)): + + if _VERSION_STRING < "2.9": + leftDown = wx.GetMouseState().LeftDown() + else: + leftDown = wx.GetMouseState().LeftIsDown() + + if leftDown: + overflow_state = AUI_BUTTON_STATE_PRESSED + else: + overflow_state = AUI_BUTTON_STATE_HOVER + + if overflow_state != self._overflow_state: + self._overflow_state = overflow_state + self.Refresh(False) + self.Update() + + self._overflow_state = overflow_state + + + def ToggleTool(self, tool_id, state): + """ + Toggles a tool on or off. This does not cause any event to get emitted. + + :param `tool_id`: tool in question. + :param `state`: if ``True``, toggles the tool on, otherwise toggles it off. + + :note: This only applies to a tool that has been specified as a toggle tool. + """ + + tool = self.FindTool(tool_id) + + if tool: + if tool.kind not in [ITEM_CHECK, ITEM_RADIO]: + return + + if tool.kind == ITEM_RADIO: + idx = self.GetToolIndex(tool_id) + if idx >= 0 and idx < len(self._items): + for i in xrange(idx, len(self._items)): + tool = self.FindToolByIndex(i) + if tool.kind != ITEM_RADIO: + break + tool.state &= ~AUI_BUTTON_STATE_CHECKED + + for i in xrange(idx, -1, -1): + tool = self.FindToolByIndex(i) + if tool.kind != ITEM_RADIO: + break + tool.state &= ~AUI_BUTTON_STATE_CHECKED + + tool = self.FindTool(tool_id) + tool.state |= AUI_BUTTON_STATE_CHECKED + else: + if state == True: + tool.state |= AUI_BUTTON_STATE_CHECKED + else: + tool.state &= ~AUI_BUTTON_STATE_CHECKED + + + def GetToolToggled(self, tool_id): + """ + Returns whether a tool is toggled or not. + + :param `tool_id`: the toolbar item identifier. + + :note: This only applies to a tool that has been specified as a toggle tool. + """ + + tool = self.FindTool(tool_id) + + if tool: + if tool.kind not in [ITEM_CHECK, ITEM_RADIO]: + return False + + return (tool.state & AUI_BUTTON_STATE_CHECKED and [True] or [False])[0] + + return False + + + def EnableTool(self, tool_id, state): + """ + Enables or disables the tool. + + :param `tool_id`: identifier for the tool to enable or disable. + :param `state`: if ``True``, enables the tool, otherwise disables it. + """ + + tool = self.FindTool(tool_id) + + if tool: + + if state == True: + tool.state &= ~AUI_BUTTON_STATE_DISABLED + else: + tool.state |= AUI_BUTTON_STATE_DISABLED + + + def GetToolEnabled(self, tool_id): + """ + Returns whether the tool identified by `tool_id` is enabled or not. + + :param `tool_id`: the tool identifier. + """ + + tool = self.FindTool(tool_id) + + if tool: + return (tool.state & AUI_BUTTON_STATE_DISABLED and [False] or [True])[0] + + return False + + + def GetToolLabel(self, tool_id): + """ + Returns the tool label for the tool identified by `tool_id`. + + :param `tool_id`: the tool identifier. + """ + + tool = self.FindTool(tool_id) + if not tool: + return "" + + return tool.label + + + def SetToolLabel(self, tool_id, label): + """ + Sets the tool label for the tool identified by `tool_id`. + + :param `tool_id`: the tool identifier; + :param `label`: the new toolbar item label. + """ + + tool = self.FindTool(tool_id) + if tool: + tool.label = label + + + def GetToolBitmap(self, tool_id): + """ + Returns the tool bitmap for the tool identified by `tool_id`. + + :param `tool_id`: the tool identifier. + """ + + tool = self.FindTool(tool_id) + if not tool: + return wx.NullBitmap + + return tool.bitmap + + + def SetToolBitmap(self, tool_id, bitmap): + """ + Sets the tool bitmap for the tool identified by `tool_id`. + + :param `tool_id`: the tool identifier; + :param `bitmap`: the new bitmap for the toolbar item (an instance of `wx.Bitmap`). + """ + + tool = self.FindTool(tool_id) + if tool: + tool.bitmap = bitmap + + + def SetToolNormalBitmap(self, tool_id, bitmap): + """ + Sets the tool bitmap for the tool identified by `tool_id`. + + :param `tool_id`: the tool identifier; + :param `bitmap`: the new bitmap for the toolbar item (an instance of `wx.Bitmap`). + """ + + self.SetToolBitmap(tool_id, bitmap) + + + def SetToolDisabledBitmap(self, tool_id, bitmap): + """ + Sets the tool disabled bitmap for the tool identified by `tool_id`. + + :param `tool_id`: the tool identifier; + :param `bitmap`: the new disabled bitmap for the toolbar item (an instance of `wx.Bitmap`). + """ + + tool = self.FindTool(tool_id) + if tool: + tool.disabled_bitmap = bitmap + + + def GetToolShortHelp(self, tool_id): + """ + Returns the short help for the given tool. + + :param `tool_id`: the tool identifier. + """ + + tool = self.FindTool(tool_id) + if not tool: + return "" + + return tool.short_help + + + def SetToolShortHelp(self, tool_id, help_string): + """ + Sets the short help for the given tool. + + :param `tool_id`: the tool identifier; + :param `help_string`: the string for the short help. + """ + + tool = self.FindTool(tool_id) + if tool: + tool.short_help = help_string + + + def GetToolLongHelp(self, tool_id): + """ + Returns the long help for the given tool. + + :param `tool_id`: the tool identifier. + """ + + tool = self.FindTool(tool_id) + if not tool: + return "" + + return tool.long_help + + + def SetToolAlignment(self, alignment=wx.EXPAND): + """ + This sets the alignment for all of the tools within the + toolbar (only has an effect when the toolbar is expanded). + + :param `alignment`: `wx.Sizer` alignment value + (``wx.ALIGN_CENTER_HORIZONTAL`` or ``wx.ALIGN_CENTER_VERTICAL``). + """ + + self._tool_alignment = alignment + + + + def SetToolLongHelp(self, tool_id, help_string): + """ + Sets the long help for the given tool. + + :param `tool_id`: the tool identifier; + :param `help_string`: the string for the long help. + """ + + tool = self.FindTool(tool_id) + if tool: + tool.long_help = help_string + + + def SetCustomOverflowItems(self, prepend, append): + """ + Sets the two lists `prepend` and `append` as custom overflow items. + + :param `prepend`: a list of L{AuiToolBarItem} to be prepended; + :param `append`: a list of L{AuiToolBarItem} to be appended. + """ + + self._custom_overflow_prepend = prepend + self._custom_overflow_append = append + + + def GetToolCount(self): + """ Returns the number of tools in the L{AuiToolBar}. """ + + return len(self._items) + + + def GetToolIndex(self, tool_id): + """ + Returns the position of the tool in the toolbar given its identifier. + + :param `tool_id`: the toolbar item identifier. + """ + + # this will prevent us from returning the index of the + # first separator in the toolbar since its id is equal to -1 + if tool_id == -1: + return wx.NOT_FOUND + + for i, item in enumerate(self._items): + if item.id == tool_id: + return i + + return wx.NOT_FOUND + + + def GetToolPos(self, tool_id): + """ + Returns the position of the tool in the toolbar given its identifier. + + :param `tool_id`: the toolbar item identifier. + """ + + return self.GetToolIndex(tool_id) + + + def GetToolFitsByIndex(self, tool_id): + """ + Returns whether the tool identified by `tool_id` fits into the toolbar or not. + + :param `tool_id`: the toolbar item identifier. + """ + + if tool_id < 0 or tool_id >= len(self._items): + return False + + if not self._items[tool_id].sizer_item: + return False + + cli_w, cli_h = self.GetClientSize() + rect = self._items[tool_id].sizer_item.GetRect() + + if self._agwStyle & AUI_TB_VERTICAL: + # take the dropdown size into account + if self._overflow_visible: + cli_h -= self._overflow_sizer_item.GetSize().y + + if rect.y+rect.height < cli_h: + return True + + else: + + # take the dropdown size into account + if self._overflow_visible: + cli_w -= self._overflow_sizer_item.GetSize().x + + if rect.x+rect.width < cli_w: + return True + + return False + + + def GetToolFits(self, tool_id): + """ + Returns whether the tool identified by `tool_id` fits into the toolbar or not. + + :param `tool_id`: the toolbar item identifier. + """ + + return self.GetToolFitsByIndex(self.GetToolIndex(tool_id)) + + + def GetToolRect(self, tool_id): + """ + Returns the toolbar item rectangle + + :param `tool_id`: the toolbar item identifier. + """ + + tool = self.FindTool(tool_id) + if tool and tool.sizer_item: + return tool.sizer_item.GetRect() + + return wx.Rect() + + + def GetToolBarFits(self): + """ Returns whether the L{AuiToolBar} size fits in a specified size. """ + + if len(self._items) == 0: + # empty toolbar always 'fits' + return True + + # entire toolbar content fits if the last tool fits + return self.GetToolFitsByIndex(len(self._items) - 1) + + + def Realize(self): + """ Realizes the toolbar. This function should be called after you have added tools. """ + + dc = wx.ClientDC(self) + + if not dc.IsOk(): + return False + + horizontal = True + if self._agwStyle & AUI_TB_VERTICAL: + horizontal = False + + # create the new sizer to add toolbar elements to + sizer = wx.BoxSizer((horizontal and [wx.HORIZONTAL] or [wx.VERTICAL])[0]) + + # add gripper area + separator_size = self._art.GetElementSize(AUI_TBART_SEPARATOR_SIZE) + gripper_size = self._art.GetElementSize(AUI_TBART_GRIPPER_SIZE) + + if gripper_size > 0 and self._gripper_visible: + if horizontal: + self._gripper_sizer_item = sizer.Add((gripper_size, 1), 0, wx.EXPAND) + else: + self._gripper_sizer_item = sizer.Add((1, gripper_size), 0, wx.EXPAND) + else: + self._gripper_sizer_item = None + + # add "left" padding + if self._left_padding > 0: + if horizontal: + sizer.Add((self._left_padding, 1)) + else: + sizer.Add((1, self._left_padding)) + + count = len(self._items) + for i, item in enumerate(self._items): + + sizer_item = None + kind = item.kind + + if kind == ITEM_LABEL: + + size = self._art.GetLabelSize(dc, self, item) + sizer_item = sizer.Add((size.x + (self._tool_border_padding*2), + size.y + (self._tool_border_padding*2)), + item.proportion, + item.alignment) + if i+1 < count: + sizer.AddSpacer(self._tool_packing) + + + elif kind in [ITEM_CHECK, ITEM_NORMAL, ITEM_RADIO]: + + size = self._art.GetToolSize(dc, self, item) + sizer_item = sizer.Add((size.x + (self._tool_border_padding*2), + size.y + (self._tool_border_padding*2)), + 0, + item.alignment) + # add tool packing + if i+1 < count: + sizer.AddSpacer(self._tool_packing) + + elif kind == ITEM_SEPARATOR: + + if horizontal: + sizer_item = sizer.Add((separator_size, 1), 0, wx.EXPAND) + else: + sizer_item = sizer.Add((1, separator_size), 0, wx.EXPAND) + + # add tool packing + if i+1 < count: + sizer.AddSpacer(self._tool_packing) + + elif kind == ITEM_SPACER: + + if item.proportion > 0: + sizer_item = sizer.AddStretchSpacer(item.proportion) + else: + sizer_item = sizer.Add((item.spacer_pixels, 1)) + + elif kind == ITEM_CONTROL: + + vert_sizer = wx.BoxSizer(wx.VERTICAL) + vert_sizer.AddStretchSpacer(1) + ctrl_sizer_item = vert_sizer.Add(item.window, 0, wx.EXPAND) + vert_sizer.AddStretchSpacer(1) + + if self._agwStyle & AUI_TB_TEXT and \ + self._tool_text_orientation == AUI_TBTOOL_TEXT_BOTTOM and \ + item.GetLabel() != "": + + s = self.GetLabelSize(item.GetLabel()) + vert_sizer.Add((1, s.y)) + + sizer_item = sizer.Add(vert_sizer, item.proportion, wx.EXPAND) + min_size = item.min_size + + # proportional items will disappear from the toolbar if + # their min width is not set to something really small + if item.proportion != 0: + min_size.x = 1 + + if min_size.IsFullySpecified(): + sizer.SetItemMinSize(vert_sizer, min_size) + vert_sizer.SetItemMinSize(item.window, min_size) + + # add tool packing + if i+1 < count: + sizer.AddSpacer(self._tool_packing) + + item.sizer_item = sizer_item + + + # add "right" padding + if self._right_padding > 0: + if horizontal: + sizer.Add((self._right_padding, 1)) + else: + sizer.Add((1, self._right_padding)) + + # add drop down area + self._overflow_sizer_item = None + + if self._agwStyle & AUI_TB_OVERFLOW: + + overflow_size = self._art.GetElementSize(AUI_TBART_OVERFLOW_SIZE) + if overflow_size > 0 and self._overflow_visible: + + if horizontal: + self._overflow_sizer_item = sizer.Add((overflow_size, 1), 0, wx.EXPAND) + else: + self._overflow_sizer_item = sizer.Add((1, overflow_size), 0, wx.EXPAND) + + else: + + self._overflow_sizer_item = None + + # the outside sizer helps us apply the "top" and "bottom" padding + outside_sizer = wx.BoxSizer((horizontal and [wx.VERTICAL] or [wx.HORIZONTAL])[0]) + + # add "top" padding + if self._top_padding > 0: + + if horizontal: + outside_sizer.Add((1, self._top_padding)) + else: + outside_sizer.Add((self._top_padding, 1)) + + # add the sizer that contains all of the toolbar elements + outside_sizer.Add(sizer, 1, self._tool_alignment) + + # add "bottom" padding + if self._bottom_padding > 0: + + if horizontal: + outside_sizer.Add((1, self._bottom_padding)) + else: + outside_sizer.Add((self._bottom_padding, 1)) + + del self._sizer # remove old sizer + self._sizer = outside_sizer + self.SetSizer(outside_sizer) + + # calculate the rock-bottom minimum size + for item in self._items: + + if item.sizer_item and item.proportion > 0 and item.min_size.IsFullySpecified(): + item.sizer_item.SetMinSize((0, 0)) + + self._absolute_min_size = self._sizer.GetMinSize() + + # reset the min sizes to what they were + for item in self._items: + + if item.sizer_item and item.proportion > 0 and item.min_size.IsFullySpecified(): + item.sizer_item.SetMinSize(item.min_size) + + # set control size + size = self._sizer.GetMinSize() + self.SetMinSize(size) + self._minWidth = size.x + self._minHeight = size.y + + if self._agwStyle & AUI_TB_NO_AUTORESIZE == 0: + + cur_size = self.GetClientSize() + new_size = self.GetMinSize() + + if new_size != cur_size: + + self.SetClientSize(new_size) + + else: + + self._sizer.SetDimension(0, 0, cur_size.x, cur_size.y) + + else: + + cur_size = self.GetClientSize() + self._sizer.SetDimension(0, 0, cur_size.x, cur_size.y) + + self.Refresh(False) + return True + + + def GetOverflowState(self): + """ Returns the state of the overflow button. """ + + return self._overflow_state + + + def GetOverflowRect(self): + """ Returns the rectangle of the overflow button. """ + + cli_rect = wx.RectPS(wx.Point(0, 0), self.GetClientSize()) + overflow_rect = wx.Rect(*self._overflow_sizer_item.GetRect()) + overflow_size = self._art.GetElementSize(AUI_TBART_OVERFLOW_SIZE) + + if self._agwStyle & AUI_TB_VERTICAL: + + overflow_rect.y = cli_rect.height - overflow_size + overflow_rect.x = 0 + overflow_rect.width = cli_rect.width + overflow_rect.height = overflow_size + + else: + + overflow_rect.x = cli_rect.width - overflow_size + overflow_rect.y = 0 + overflow_rect.width = overflow_size + overflow_rect.height = cli_rect.height + + return overflow_rect + + + def GetLabelSize(self, label): + """ + Returns the standard size of a toolbar item. + + :param `label`: a test label. + """ + + dc = wx.ClientDC(self) + dc.SetFont(self._font) + + return GetLabelSize(dc, label, self._tool_orientation != AUI_TBTOOL_HORIZONTAL) + + + def GetAuiManager(self): + """ Returns the L{AuiManager} which manages the toolbar. """ + + try: + return self._auiManager + except AttributeError: + return False + + + def SetAuiManager(self, auiManager): + """ Sets the L{AuiManager} which manages the toolbar. """ + + self._auiManager = auiManager + + + def DoIdleUpdate(self): + """ Updates the toolbar during idle times. """ + + handler = self.GetEventHandler() + if not handler: + return + + need_refresh = False + + for item in self._items: + + if item.id == -1: + continue + + evt = wx.UpdateUIEvent(item.id) + evt.SetEventObject(self) + + if handler.ProcessEvent(evt): + + if evt.GetSetEnabled(): + + if item.window: + is_enabled = item.window.IsEnabled() + else: + is_enabled = (item.state & AUI_BUTTON_STATE_DISABLED and [False] or [True])[0] + + new_enabled = evt.GetEnabled() + if new_enabled != is_enabled: + + if item.window: + item.window.Enable(new_enabled) + else: + if new_enabled: + item.state &= ~AUI_BUTTON_STATE_DISABLED + else: + item.state |= AUI_BUTTON_STATE_DISABLED + + need_refresh = True + + if evt.GetSetChecked(): + + # make sure we aren't checking an item that can't be + if item.kind != ITEM_CHECK and item.kind != ITEM_RADIO: + continue + + is_checked = (item.state & AUI_BUTTON_STATE_CHECKED and [True] or [False])[0] + new_checked = evt.GetChecked() + + if new_checked != is_checked: + + if new_checked: + item.state |= AUI_BUTTON_STATE_CHECKED + else: + item.state &= ~AUI_BUTTON_STATE_CHECKED + + need_refresh = True + + if need_refresh: + self.Refresh(False) + + + def OnSize(self, event): + """ + Handles the ``wx.EVT_SIZE`` event for L{AuiToolBar}. + + :param `event`: a `wx.SizeEvent` event to be processed. + """ + + x, y = self.GetClientSize() + realize = False + + if x > y: + self.SetOrientation(wx.HORIZONTAL) + else: + self.SetOrientation(wx.VERTICAL) + + if (x >= y and self._absolute_min_size.x > x) or (y > x and self._absolute_min_size.y > y): + + # hide all flexible items + for item in self._items: + if item.sizer_item and item.proportion > 0 and item.sizer_item.IsShown(): + item.sizer_item.Show(False) + item.sizer_item.SetProportion(0) + + if self._originalStyle & AUI_TB_OVERFLOW: + if not self.GetOverflowVisible(): + self.SetOverflowVisible(True) + realize = True + + else: + + if self._originalStyle & AUI_TB_OVERFLOW and not self._custom_overflow_append and \ + not self._custom_overflow_prepend: + if self.GetOverflowVisible(): + self.SetOverflowVisible(False) + realize = True + + # show all flexible items + for item in self._items: + if item.sizer_item and item.proportion > 0 and not item.sizer_item.IsShown(): + item.sizer_item.Show(True) + item.sizer_item.SetProportion(item.proportion) + + self._sizer.SetDimension(0, 0, x, y) + + if realize: + self.Realize() + else: + self.Refresh(False) + + self.Update() + + + def DoSetSize(self, x, y, width, height, sizeFlags=wx.SIZE_AUTO): + """ + Sets the position and size of the window in pixels. The `sizeFlags` + parameter indicates the interpretation of the other params if they are + equal to -1. + + :param `x`: the window `x` position; + :param `y`: the window `y` position; + :param `width`: the window width; + :param `height`: the window height; + :param `sizeFlags`: may have one of this bit set: + + =================================== ====================================== + Size Flags Description + =================================== ====================================== + ``wx.SIZE_AUTO`` A -1 indicates that a class-specific default should be used. + ``wx.SIZE_AUTO_WIDTH`` A -1 indicates that a class-specific default should be used for the width. + ``wx.SIZE_AUTO_HEIGHT`` A -1 indicates that a class-specific default should be used for the height. + ``wx.SIZE_USE_EXISTING`` Existing dimensions should be used if -1 values are supplied. + ``wx.SIZE_ALLOW_MINUS_ONE`` Allow dimensions of -1 and less to be interpreted as real dimensions, not default values. + ``wx.SIZE_FORCE`` Normally, if the position and the size of the window are already the same as the parameters of this function, nothing is done. but with this flag a window resize may be forced even in this case (supported in wx 2.6.2 and later and only implemented for MSW and ignored elsewhere currently) + =================================== ====================================== + + :note: Overridden from `wx.PyControl`. + """ + + parent_size = self.GetParent().GetClientSize() + if x + width > parent_size.x: + width = max(0, parent_size.x - x) + if y + height > parent_size.y: + height = max(0, parent_size.y - y) + + wx.PyControl.DoSetSize(self, x, y, width, height, sizeFlags) + + + def OnIdle(self, event): + """ + Handles the ``wx.EVT_IDLE`` event for L{AuiToolBar}. + + :param `event`: a `wx.IdleEvent` event to be processed. + """ + + self.DoIdleUpdate() + event.Skip() + + + def DoGetBestSize(self): + """ + Gets the size which best suits the window: for a control, it would be the + minimal size which doesn't truncate the control, for a panel - the same + size as it would have after a call to `Fit()`. + + :note: Overridden from `wx.PyControl`. + """ + + return self._absolute_min_size + + + def OnPaint(self, event): + """ + Handles the ``wx.EVT_PAINT`` event for L{AuiToolBar}. + + :param `event`: a `wx.PaintEvent` event to be processed. + """ + + dc = wx.AutoBufferedPaintDC(self) + cli_rect = wx.RectPS(wx.Point(0, 0), self.GetClientSize()) + + horizontal = True + if self._agwStyle & AUI_TB_VERTICAL: + horizontal = False + + if self._agwStyle & AUI_TB_PLAIN_BACKGROUND: + self._art.DrawPlainBackground(dc, self, cli_rect) + else: + self._art.DrawBackground(dc, self, cli_rect, horizontal) + + gripper_size = self._art.GetElementSize(AUI_TBART_GRIPPER_SIZE) + dropdown_size = self._art.GetElementSize(AUI_TBART_OVERFLOW_SIZE) + + # paint the gripper + if gripper_size > 0 and self._gripper_sizer_item: + gripper_rect = wx.Rect(*self._gripper_sizer_item.GetRect()) + if horizontal: + gripper_rect.width = gripper_size + else: + gripper_rect.height = gripper_size + + self._art.DrawGripper(dc, self, gripper_rect) + + # calculated how far we can draw items + if horizontal: + last_extent = cli_rect.width + else: + last_extent = cli_rect.height + + if self._overflow_visible: + last_extent -= dropdown_size + + # paint each individual tool + for item in self._items: + + if not item.sizer_item: + continue + + item_rect = wx.Rect(*item.sizer_item.GetRect()) + + if (horizontal and item_rect.x + item_rect.width >= last_extent) or \ + (not horizontal and item_rect.y + item_rect.height >= last_extent): + + break + + if item.kind == ITEM_SEPARATOR: + # draw a separator + self._art.DrawSeparator(dc, self, item_rect) + + elif item.kind == ITEM_LABEL: + # draw a text label only + self._art.DrawLabel(dc, self, item, item_rect) + + elif item.kind == ITEM_NORMAL: + # draw a regular button or dropdown button + if not item.dropdown: + self._art.DrawButton(dc, self, item, item_rect) + else: + self._art.DrawDropDownButton(dc, self, item, item_rect) + + elif item.kind == ITEM_CHECK: + # draw a regular toggle button or a dropdown one + if not item.dropdown: + self._art.DrawButton(dc, self, item, item_rect) + else: + self._art.DrawDropDownButton(dc, self, item, item_rect) + + elif item.kind == ITEM_RADIO: + # draw a toggle button + self._art.DrawButton(dc, self, item, item_rect) + + elif item.kind == ITEM_CONTROL: + # draw the control's label + self._art.DrawControlLabel(dc, self, item, item_rect) + + # fire a signal to see if the item wants to be custom-rendered + self.OnCustomRender(dc, item, item_rect) + + # paint the overflow button + if dropdown_size > 0 and self._overflow_sizer_item: + dropdown_rect = self.GetOverflowRect() + self._art.DrawOverflowButton(dc, self, dropdown_rect, self._overflow_state) + + + def OnEraseBackground(self, event): + """ + Handles the ``wx.EVT_ERASE_BACKGROUND`` event for L{AuiToolBar}. + + :param `event`: a `wx.EraseEvent` event to be processed. + + :note: This is intentionally empty, to reduce flicker. + """ + + pass + + + def OnLeftDown(self, event): + """ + Handles the ``wx.EVT_LEFT_DOWN`` event for L{AuiToolBar}. + + :param `event`: a `wx.MouseEvent` event to be processed. + """ + + cli_rect = wx.RectPS(wx.Point(0, 0), self.GetClientSize()) + self.StopPreviewTimer() + + if self._gripper_sizer_item: + + gripper_rect = wx.Rect(*self._gripper_sizer_item.GetRect()) + if gripper_rect.Contains(event.GetPosition()): + + # find aui manager + manager = self.GetAuiManager() + if not manager: + return + + x_drag_offset = event.GetX() - gripper_rect.GetX() + y_drag_offset = event.GetY() - gripper_rect.GetY() + + clientPt = wx.Point(*event.GetPosition()) + screenPt = self.ClientToScreen(clientPt) + managedWindow = manager.GetManagedWindow() + managerClientPt = managedWindow.ScreenToClient(screenPt) + + # gripper was clicked + manager.OnGripperClicked(self, managerClientPt, wx.Point(x_drag_offset, y_drag_offset)) + return + + if self._overflow_sizer_item: + overflow_rect = self.GetOverflowRect() + + if self._art and self._overflow_visible and overflow_rect.Contains(event.GetPosition()): + + e = AuiToolBarEvent(wxEVT_COMMAND_AUITOOLBAR_OVERFLOW_CLICK, -1) + e.SetEventObject(self) + e.SetToolId(-1) + e.SetClickPoint(event.GetPosition()) + processed = self.ProcessEvent(e) + + if processed: + self.DoIdleUpdate() + else: + overflow_items = [] + + # add custom overflow prepend items, if any + count = len(self._custom_overflow_prepend) + for i in xrange(count): + overflow_items.append(self._custom_overflow_prepend[i]) + + # only show items that don't fit in the dropdown + count = len(self._items) + for i in xrange(count): + + if not self.GetToolFitsByIndex(i): + overflow_items.append(self._items[i]) + + # add custom overflow append items, if any + count = len(self._custom_overflow_append) + for i in xrange(count): + overflow_items.append(self._custom_overflow_append[i]) + + res = self._art.ShowDropDown(self, overflow_items) + self._overflow_state = 0 + self.Refresh(False) + if res != -1: + e = wx.CommandEvent(wx.wxEVT_COMMAND_MENU_SELECTED, res) + e.SetEventObject(self) + if not self.GetParent().ProcessEvent(e): + tool = self.FindTool(res) + if tool: + state = (tool.state & AUI_BUTTON_STATE_CHECKED and [True] or [False])[0] + self.ToggleTool(res, not state) + + return + + self._dragging = False + self._action_pos = wx.Point(*event.GetPosition()) + self._action_item = self.FindToolForPosition(*event.GetPosition()) + + if self._action_item: + + if self._action_item.state & AUI_BUTTON_STATE_DISABLED: + + self._action_pos = wx.Point(-1, -1) + self._action_item = None + return + + self.SetPressedItem(self._action_item) + + # fire the tool dropdown event + e = AuiToolBarEvent(wxEVT_COMMAND_AUITOOLBAR_TOOL_DROPDOWN, self._action_item.id) + e.SetEventObject(self) + e.SetToolId(self._action_item.id) + e.SetDropDownClicked(False) + + mouse_x, mouse_y = event.GetX(), event.GetY() + rect = wx.Rect(*self._action_item.sizer_item.GetRect()) + + if self._action_item.dropdown: + if (self._action_item.orientation == AUI_TBTOOL_HORIZONTAL and \ + mouse_x >= (rect.x+rect.width-BUTTON_DROPDOWN_WIDTH-1) and \ + mouse_x < (rect.x+rect.width)) or \ + (self._action_item.orientation != AUI_TBTOOL_HORIZONTAL and \ + mouse_y >= (rect.y+rect.height-BUTTON_DROPDOWN_WIDTH-1) and \ + mouse_y < (rect.y+rect.height)): + + e.SetDropDownClicked(True) + + e.SetClickPoint(event.GetPosition()) + e.SetItemRect(rect) + self.ProcessEvent(e) + self.DoIdleUpdate() + + + def OnLeftUp(self, event): + """ + Handles the ``wx.EVT_LEFT_UP`` event for L{AuiToolBar}. + + :param `event`: a `wx.MouseEvent` event to be processed. + """ + + self.SetPressedItem(None) + + hit_item = self.FindToolForPosition(*event.GetPosition()) + + if hit_item and not hit_item.state & AUI_BUTTON_STATE_DISABLED: + self.SetHoverItem(hit_item) + + if self._dragging: + # reset drag and drop member variables + self._dragging = False + self._action_pos = wx.Point(-1, -1) + self._action_item = None + + else: + + if self._action_item and hit_item == self._action_item: + self.SetToolTipString("") + + if hit_item.kind in [ITEM_CHECK, ITEM_RADIO]: + toggle = not (self._action_item.state & AUI_BUTTON_STATE_CHECKED) + self.ToggleTool(self._action_item.id, toggle) + + # repaint immediately + self.Refresh(False) + self.Update() + + e = wx.CommandEvent(wx.wxEVT_COMMAND_MENU_SELECTED, self._action_item.id) + e.SetEventObject(self) + e.SetInt(toggle) + self._action_pos = wx.Point(-1, -1) + self._action_item = None + + self.ProcessEvent(e) + self.DoIdleUpdate() + + else: + + if self._action_item.id == ID_RESTORE_FRAME: + # find aui manager + manager = self.GetAuiManager() + + if not manager: + return + + pane = manager.GetPane(self) + e = framemanager.AuiManagerEvent(framemanager.wxEVT_AUI_PANE_MIN_RESTORE) + + e.SetManager(manager) + e.SetPane(pane) + + manager.ProcessEvent(e) + self.DoIdleUpdate() + + else: + + e = wx.CommandEvent(wx.wxEVT_COMMAND_MENU_SELECTED, self._action_item.id) + e.SetEventObject(self) + self.ProcessEvent(e) + self.DoIdleUpdate() + + # reset drag and drop member variables + self._dragging = False + self._action_pos = wx.Point(-1, -1) + self._action_item = None + + + def OnRightDown(self, event): + """ + Handles the ``wx.EVT_RIGHT_DOWN`` event for L{AuiToolBar}. + + :param `event`: a `wx.MouseEvent` event to be processed. + """ + + cli_rect = wx.RectPS(wx.Point(0, 0), self.GetClientSize()) + + if self._gripper_sizer_item: + gripper_rect = self._gripper_sizer_item.GetRect() + if gripper_rect.Contains(event.GetPosition()): + return + + if self._overflow_sizer_item: + + dropdown_size = self._art.GetElementSize(AUI_TBART_OVERFLOW_SIZE) + if dropdown_size > 0 and event.m_x > cli_rect.width - dropdown_size and \ + event.m_y >= 0 and event.m_y < cli_rect.height and self._art: + return + + self._action_pos = wx.Point(*event.GetPosition()) + self._action_item = self.FindToolForPosition(*event.GetPosition()) + + if self._action_item: + if self._action_item.state & AUI_BUTTON_STATE_DISABLED: + + self._action_pos = wx.Point(-1, -1) + self._action_item = None + return + + + def OnRightUp(self, event): + """ + Handles the ``wx.EVT_RIGHT_UP`` event for L{AuiToolBar}. + + :param `event`: a `wx.MouseEvent` event to be processed. + """ + + hit_item = self.FindToolForPosition(*event.GetPosition()) + + if self._action_item and hit_item == self._action_item: + + e = AuiToolBarEvent(wxEVT_COMMAND_AUITOOLBAR_RIGHT_CLICK, self._action_item.id) + e.SetEventObject(self) + e.SetToolId(self._action_item.id) + e.SetClickPoint(self._action_pos) + self.ProcessEvent(e) + self.DoIdleUpdate() + + else: + + # right-clicked on the invalid area of the toolbar + e = AuiToolBarEvent(wxEVT_COMMAND_AUITOOLBAR_RIGHT_CLICK, -1) + e.SetEventObject(self) + e.SetToolId(-1) + e.SetClickPoint(self._action_pos) + self.ProcessEvent(e) + self.DoIdleUpdate() + + # reset member variables + self._action_pos = wx.Point(-1, -1) + self._action_item = None + + + def OnMiddleDown(self, event): + """ + Handles the ``wx.EVT_MIDDLE_DOWN`` event for L{AuiToolBar}. + + :param `event`: a `wx.MouseEvent` event to be processed. + """ + + cli_rect = wx.RectPS(wx.Point(0, 0), self.GetClientSize()) + + if self._gripper_sizer_item: + + gripper_rect = self._gripper_sizer_item.GetRect() + if gripper_rect.Contains(event.GetPosition()): + return + + if self._overflow_sizer_item: + + dropdown_size = self._art.GetElementSize(AUI_TBART_OVERFLOW_SIZE) + if dropdown_size > 0 and event.m_x > cli_rect.width - dropdown_size and \ + event.m_y >= 0 and event.m_y < cli_rect.height and self._art: + return + + self._action_pos = wx.Point(*event.GetPosition()) + self._action_item = self.FindToolForPosition(*event.GetPosition()) + + if self._action_item: + if self._action_item.state & AUI_BUTTON_STATE_DISABLED: + + self._action_pos = wx.Point(-1, -1) + self._action_item = None + return + + + def OnMiddleUp(self, event): + """ + Handles the ``wx.EVT_MIDDLE_UP`` event for L{AuiToolBar}. + + :param `event`: a `wx.MouseEvent` event to be processed. + """ + + hit_item = self.FindToolForPosition(*event.GetPosition()) + + if self._action_item and hit_item == self._action_item: + if hit_item.kind == ITEM_NORMAL: + + e = AuiToolBarEvent(wxEVT_COMMAND_AUITOOLBAR_MIDDLE_CLICK, self._action_item.id) + e.SetEventObject(self) + e.SetToolId(self._action_item.id) + e.SetClickPoint(self._action_pos) + self.ProcessEvent(e) + self.DoIdleUpdate() + + # reset member variables + self._action_pos = wx.Point(-1, -1) + self._action_item = None + + + def OnMotion(self, event): + """ + Handles the ``wx.EVT_MOTION`` event for L{AuiToolBar}. + + :param `event`: a `wx.MouseEvent` event to be processed. + """ + + # start a drag event + if not self._dragging and self._action_item != None and self._action_pos != wx.Point(-1, -1) and \ + abs(event.m_x - self._action_pos.x) + abs(event.m_y - self._action_pos.y) > 5: + + self.SetToolTipString("") + self._dragging = True + + e = AuiToolBarEvent(wxEVT_COMMAND_AUITOOLBAR_BEGIN_DRAG, self.GetId()) + e.SetEventObject(self) + e.SetToolId(self._action_item.id) + self.ProcessEvent(e) + self.DoIdleUpdate() + return + + hit_item = self.FindToolForPosition(*event.GetPosition()) + + if hit_item: + if not hit_item.state & AUI_BUTTON_STATE_DISABLED: + self.SetHoverItem(hit_item) + else: + self.SetHoverItem(None) + + else: + # no hit item, remove any hit item + self.SetHoverItem(hit_item) + + # figure out tooltips + packing_hit_item = self.FindToolForPositionWithPacking(*event.GetPosition()) + + if packing_hit_item: + + if packing_hit_item != self._tip_item: + self._tip_item = packing_hit_item + + if packing_hit_item.short_help != "": + self.StartPreviewTimer() + self.SetToolTipString(packing_hit_item.short_help) + else: + self.SetToolTipString("") + self.StopPreviewTimer() + + else: + + self.SetToolTipString("") + self._tip_item = None + self.StopPreviewTimer() + + # if we've pressed down an item and we're hovering + # over it, make sure it's state is set to pressed + if self._action_item: + + if self._action_item == hit_item: + self.SetPressedItem(self._action_item) + else: + self.SetPressedItem(None) + + # figure out the dropdown button state (are we hovering or pressing it?) + self.RefreshOverflowState() + + + def OnLeaveWindow(self, event): + """ + Handles the ``wx.EVT_LEAVE_WINDOW`` event for L{AuiToolBar}. + + :param `event`: a `wx.MouseEvent` event to be processed. + """ + + self.RefreshOverflowState() + self.SetHoverItem(None) + self.SetPressedItem(None) + + self._tip_item = None + self.StopPreviewTimer() + + + def OnSetCursor(self, event): + """ + Handles the ``wx.EVT_SET_CURSOR`` event for L{AuiToolBar}. + + :param `event`: a `wx.SetCursorEvent` event to be processed. + """ + + cursor = wx.NullCursor + + if self._gripper_sizer_item: + + gripper_rect = self._gripper_sizer_item.GetRect() + if gripper_rect.Contains((event.GetX(), event.GetY())): + cursor = wx.StockCursor(wx.CURSOR_SIZING) + + event.SetCursor(cursor) + + + def OnCustomRender(self, dc, item, rect): + """ + Handles custom render for single L{AuiToolBar} items. + + :param `dc`: a `wx.DC` device context; + :param `item`: an instance of L{AuiToolBarItem}; + :param `rect`: the toolbar item rect. + + :note: This method must be overridden to provide custom rendering of items. + """ + + pass + + + def IsPaneMinimized(self): + """ Returns whether this L{AuiToolBar} contains a minimized pane tool. """ + + manager = self.GetAuiManager() + if not manager: + return False + + if manager.GetAGWFlags() & AUI_MGR_PREVIEW_MINIMIZED_PANES == 0: + # No previews here + return False + + self_name = manager.GetPane(self).name + + if not self_name.endswith("_min"): + # Wrong tool name + return False + + return self_name[0:-4] + + + def StartPreviewTimer(self): + """ Starts a timer in L{AuiManager} to slide-in/slide-out the minimized pane. """ + + self_name = self.IsPaneMinimized() + if not self_name: + return + + manager = self.GetAuiManager() + manager.StartPreviewTimer(self) + + + def StopPreviewTimer(self): + """ Stops a timer in L{AuiManager} to slide-in/slide-out the minimized pane. """ + + self_name = self.IsPaneMinimized() + if not self_name: + return + + manager = self.GetAuiManager() + manager.StopPreviewTimer() + diff --git a/agw/aui/auibar.pyc b/agw/aui/auibar.pyc new file mode 100644 index 0000000000000000000000000000000000000000..de1403ee98cf1cff18722da63d1f3fbd4a39573c GIT binary patch literal 115949 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 len(self._pages): + self._pages.append(page_info) + else: + self._pages.insert(idx, page_info) + + # let the art provider know how many pages we have + if self._art: + minMaxTabWidth = self._auiNotebook.GetMinMaxTabWidth() + self._art.SetSizingInfo(self._rect.GetSize(), len(self._pages), minMaxTabWidth) + + return True + + + def MovePage(self, page, new_idx): + """ + Moves a page in a new position specified by `new_idx`. + + :param `page`: the window associated with this tab; + :param `new_idx`: the new page position. + """ + + idx = self.GetIdxFromWindow(page) + if idx == -1: + return False + + # get page entry, make a copy of it + p = self.GetPage(idx) + + # remove old page entry + self.RemovePage(page) + + # insert page where it should be + self.InsertPage(page, p, new_idx) + + return True + + + def RemovePage(self, wnd): + """ + Removes a page from the tab control. + + :param `wnd`: an instance of `wx.Window`, a window associated with this tab. + """ + + minMaxTabWidth = self._auiNotebook.GetMinMaxTabWidth() + + for page in self._pages: + if page.window == wnd: + self._pages.remove(page) + self._tab_offset = min(self._tab_offset, len(self._pages) - 1) + + # let the art provider know how many pages we have + if self._art: + self._art.SetSizingInfo(self._rect.GetSize(), len(self._pages), minMaxTabWidth) + + return True + + return False + + + def SetActivePage(self, wndOrInt): + """ + Sets the L{AuiTabContainer} active page. + + :param `wndOrInt`: an instance of `wx.Window` or an integer specifying a tab index. + """ + + if type(wndOrInt) == types.IntType: + + if wndOrInt >= len(self._pages): + return False + + wnd = self._pages[wndOrInt].window + + else: + wnd = wndOrInt + + found = False + + for indx, page in enumerate(self._pages): + if page.window == wnd: + page.active = True + found = True + else: + page.active = False + + return found + + + def SetNoneActive(self): + """ Sets all the tabs as inactive (non-selected). """ + + for page in self._pages: + page.active = False + + + def GetActivePage(self): + """ Returns the current selected tab or ``wx.NOT_FOUND`` if none is selected. """ + + for indx, page in enumerate(self._pages): + if page.active: + return indx + + return wx.NOT_FOUND + + + def GetWindowFromIdx(self, idx): + """ + Returns the window associated with the tab with index `idx`. + + :param `idx`: the tab index. + """ + + if idx >= len(self._pages): + return None + + return self._pages[idx].window + + + def GetIdxFromWindow(self, wnd): + """ + Returns the tab index based on the window `wnd` associated with it. + + :param `wnd`: an instance of `wx.Window`. + """ + + for indx, page in enumerate(self._pages): + if page.window == wnd: + return indx + + return wx.NOT_FOUND + + + def GetPage(self, idx): + """ + Returns the page specified by the given index. + + :param `idx`: the tab index. + """ + + if idx < 0 or idx >= len(self._pages): + raise Exception("Invalid Page index") + + return self._pages[idx] + + + def GetPages(self): + """ Returns a list of all the pages in this L{AuiTabContainer}. """ + + return self._pages + + + def GetPageCount(self): + """ Returns the number of pages in the L{AuiTabContainer}. """ + + return len(self._pages) + + + def GetEnabled(self, idx): + """ + Returns whether a tab is enabled or not. + + :param `idx`: the tab index. + """ + + if idx < 0 or idx >= len(self._pages): + return False + + return self._pages[idx].enabled + + + def EnableTab(self, idx, enable=True): + """ + Enables/disables a tab in the L{AuiTabContainer}. + + :param `idx`: the tab index; + :param `enable`: ``True`` to enable a tab, ``False`` to disable it. + """ + + if idx < 0 or idx >= len(self._pages): + raise Exception("Invalid Page index") + + self._pages[idx].enabled = enable + wnd = self.GetWindowFromIdx(idx) + wnd.Enable(enable) + + + def AddButton(self, id, location, normal_bitmap=wx.NullBitmap, disabled_bitmap=wx.NullBitmap): + """ + Adds a button in the tab area. + + :param `id`: the button identifier. This can be one of the following: + + ============================== ================================= + Button Identifier Description + ============================== ================================= + ``AUI_BUTTON_CLOSE`` Shows a close button on the tab area + ``AUI_BUTTON_WINDOWLIST`` Shows a window list button on the tab area + ``AUI_BUTTON_LEFT`` Shows a left button on the tab area + ``AUI_BUTTON_RIGHT`` Shows a right button on the tab area + ============================== ================================= + + :param `location`: the button location. Can be ``wx.LEFT`` or ``wx.RIGHT``; + :param `normal_bitmap`: the bitmap for an enabled tab; + :param `disabled_bitmap`: the bitmap for a disabled tab. + """ + + button = AuiTabContainerButton() + button.id = id + button.bitmap = normal_bitmap + button.dis_bitmap = disabled_bitmap + button.location = location + button.cur_state = AUI_BUTTON_STATE_NORMAL + + self._buttons.append(button) + + + def RemoveButton(self, id): + """ + Removes a button from the tab area. + + :param `id`: the button identifier. See L{AddButton} for a list of button identifiers. + + :see: L{AddButton} + """ + + for button in self._buttons: + if button.id == id: + self._buttons.remove(button) + return + + + def GetTabOffset(self): + """ Returns the tab offset. """ + + return self._tab_offset + + + def SetTabOffset(self, offset): + """ + Sets the tab offset. + + :param `offset`: the tab offset. + """ + + self._tab_offset = offset + + + def MinimizeTabOffset(self, dc, wnd, max_width): + """ + Minimize `self._tab_offset` to fit as many tabs as possible in the available space. + + :param `dc`: a `wx.DC` device context; + :param `wnd`: an instance of `wx.Window`; + :param `max_width`: the maximum available width for the tabs. + """ + + total_width = 0 + + for i, page in reversed(list(enumerate(self._pages))): + + tab_button = self._tab_close_buttons[i] + (tab_width, tab_height), x_extent = self._art.GetTabSize(dc, wnd, page.caption, page.bitmap, page.active, tab_button.cur_state, page.control) + total_width += tab_width + + if total_width > max_width: + + tab_offset = i + 1 + + if tab_offset < self._tab_offset and tab_offset < len(self._pages): + self._tab_offset = tab_offset + + break + + if i == 0: + self._tab_offset = 0 + + + def Render(self, raw_dc, wnd): + """ + Renders the tab catalog to the specified `wx.DC`. + + It is a virtual function and can be overridden to provide custom drawing + capabilities. + + :param `raw_dc`: a `wx.DC` device context; + :param `wnd`: an instance of `wx.Window`. + """ + + if not raw_dc or not raw_dc.IsOk(): + return + + dc = wx.MemoryDC() + + # use the same layout direction as the window DC uses to ensure that the + # text is rendered correctly + dc.SetLayoutDirection(raw_dc.GetLayoutDirection()) + + page_count = len(self._pages) + button_count = len(self._buttons) + + # create off-screen bitmap + bmp = wx.EmptyBitmap(self._rect.GetWidth(), self._rect.GetHeight()) + dc.SelectObject(bmp) + + if not dc.IsOk(): + return + + # find out if size of tabs is larger than can be + # afforded on screen + total_width = visible_width = 0 + + for i in xrange(page_count): + page = self._pages[i] + + # determine if a close button is on this tab + close_button = False + if (self._agwFlags & AUI_NB_CLOSE_ON_ALL_TABS and page.hasCloseButton) or \ + (self._agwFlags & AUI_NB_CLOSE_ON_ACTIVE_TAB and page.active and page.hasCloseButton): + + close_button = True + + control = page.control + if control: + try: + control.GetSize() + except wx.PyDeadObjectError: + page.control = None + + size, x_extent = self._art.GetTabSize(dc, wnd, page.caption, page.bitmap, page.active, + (close_button and [AUI_BUTTON_STATE_NORMAL] or \ + [AUI_BUTTON_STATE_HIDDEN])[0], page.control) + + if i+1 < page_count: + total_width += x_extent + else: + total_width += size[0] + + if i >= self._tab_offset: + if i+1 < page_count: + visible_width += x_extent + else: + visible_width += size[0] + + if total_width > self._rect.GetWidth() or self._tab_offset != 0: + + # show left/right buttons + for button in self._buttons: + if button.id == AUI_BUTTON_LEFT or \ + button.id == AUI_BUTTON_RIGHT: + + button.cur_state &= ~AUI_BUTTON_STATE_HIDDEN + + else: + + # hide left/right buttons + for button in self._buttons: + if button.id == AUI_BUTTON_LEFT or \ + button.id == AUI_BUTTON_RIGHT: + + button.cur_state |= AUI_BUTTON_STATE_HIDDEN + + # determine whether left button should be enabled + for button in self._buttons: + if button.id == AUI_BUTTON_LEFT: + if self._tab_offset == 0: + button.cur_state |= AUI_BUTTON_STATE_DISABLED + else: + button.cur_state &= ~AUI_BUTTON_STATE_DISABLED + + if button.id == AUI_BUTTON_RIGHT: + if visible_width < self._rect.GetWidth() - 16*button_count: + button.cur_state |= AUI_BUTTON_STATE_DISABLED + else: + button.cur_state &= ~AUI_BUTTON_STATE_DISABLED + + # draw background + self._art.DrawBackground(dc, wnd, self._rect) + + # draw buttons + left_buttons_width = 0 + right_buttons_width = 0 + + # draw the buttons on the right side + offset = self._rect.x + self._rect.width + + for i in xrange(button_count): + button = self._buttons[button_count - i - 1] + + if button.location != wx.RIGHT: + continue + if button.cur_state & AUI_BUTTON_STATE_HIDDEN: + continue + + button_rect = wx.Rect(*self._rect) + button_rect.SetY(1) + button_rect.SetWidth(offset) + + button.rect = self._art.DrawButton(dc, wnd, button_rect, button, wx.RIGHT) + + offset -= button.rect.GetWidth() + right_buttons_width += button.rect.GetWidth() + + offset = 0 + + # draw the buttons on the left side + for i in xrange(button_count): + button = self._buttons[button_count - i - 1] + + if button.location != wx.LEFT: + continue + if button.cur_state & AUI_BUTTON_STATE_HIDDEN: + continue + + button_rect = wx.Rect(offset, 1, 1000, self._rect.height) + + button.rect = self._art.DrawButton(dc, wnd, button_rect, button, wx.LEFT) + + offset += button.rect.GetWidth() + left_buttons_width += button.rect.GetWidth() + + offset = left_buttons_width + + if offset == 0: + offset += self._art.GetIndentSize() + + # prepare the tab-close-button array + # make sure tab button entries which aren't used are marked as hidden + for i in xrange(page_count, len(self._tab_close_buttons)): + self._tab_close_buttons[i].cur_state = AUI_BUTTON_STATE_HIDDEN + + # make sure there are enough tab button entries to accommodate all tabs + while len(self._tab_close_buttons) < page_count: + tempbtn = AuiTabContainerButton() + tempbtn.id = AUI_BUTTON_CLOSE + tempbtn.location = wx.CENTER + tempbtn.cur_state = AUI_BUTTON_STATE_HIDDEN + self._tab_close_buttons.append(tempbtn) + + # buttons before the tab offset must be set to hidden + for i in xrange(self._tab_offset): + self._tab_close_buttons[i].cur_state = AUI_BUTTON_STATE_HIDDEN + if self._pages[i].control: + if self._pages[i].control.IsShown(): + self._pages[i].control.Hide() + + self.MinimizeTabOffset(dc, wnd, self._rect.GetWidth() - right_buttons_width - offset - 2) + + # draw the tabs + active = 999 + active_offset = 0 + + rect = wx.Rect(*self._rect) + rect.y = 0 + rect.height = self._rect.height + + for i in xrange(self._tab_offset, page_count): + + page = self._pages[i] + tab_button = self._tab_close_buttons[i] + + # determine if a close button is on this tab + if (self._agwFlags & AUI_NB_CLOSE_ON_ALL_TABS and page.hasCloseButton) or \ + (self._agwFlags & AUI_NB_CLOSE_ON_ACTIVE_TAB and page.active and page.hasCloseButton): + + if tab_button.cur_state == AUI_BUTTON_STATE_HIDDEN: + + tab_button.id = AUI_BUTTON_CLOSE + tab_button.cur_state = AUI_BUTTON_STATE_NORMAL + tab_button.location = wx.CENTER + + else: + + tab_button.cur_state = AUI_BUTTON_STATE_HIDDEN + + rect.x = offset + rect.width = self._rect.width - right_buttons_width - offset - 2 + + if rect.width <= 0: + break + + page.rect, tab_button.rect, x_extent = self._art.DrawTab(dc, wnd, page, rect, tab_button.cur_state) + + if page.active: + active = i + active_offset = offset + active_rect = wx.Rect(*rect) + + offset += x_extent + + lenPages = len(self._pages) + # make sure to deactivate buttons which are off the screen to the right + for j in xrange(i+1, len(self._tab_close_buttons)): + self._tab_close_buttons[j].cur_state = AUI_BUTTON_STATE_HIDDEN + if j > 0 and j <= lenPages: + if self._pages[j-1].control: + if self._pages[j-1].control.IsShown(): + self._pages[j-1].control.Hide() + + # draw the active tab again so it stands in the foreground + if active >= self._tab_offset and active < len(self._pages): + + page = self._pages[active] + tab_button = self._tab_close_buttons[active] + + rect.x = active_offset + dummy = self._art.DrawTab(dc, wnd, page, active_rect, tab_button.cur_state) + + raw_dc.Blit(self._rect.x, self._rect.y, self._rect.GetWidth(), self._rect.GetHeight(), dc, 0, 0) + + + def IsTabVisible(self, tabPage, tabOffset, dc, wnd): + """ + Returns whether a tab is visible or not. + + :param `tabPage`: the tab index; + :param `tabOffset`: the tab offset; + :param `dc`: a `wx.DC` device context; + :param `wnd`: an instance of `wx.Window` derived window. + """ + + if not dc or not dc.IsOk(): + return False + + page_count = len(self._pages) + button_count = len(self._buttons) + self.Render(dc, wnd) + + # Hasn't been rendered yet assume it's visible + if len(self._tab_close_buttons) < page_count: + return True + + if self._agwFlags & AUI_NB_SCROLL_BUTTONS: + # First check if both buttons are disabled - if so, there's no need to + # check further for visibility. + arrowButtonVisibleCount = 0 + for i in xrange(button_count): + + button = self._buttons[i] + if button.id == AUI_BUTTON_LEFT or \ + button.id == AUI_BUTTON_RIGHT: + + if button.cur_state & AUI_BUTTON_STATE_HIDDEN == 0: + arrowButtonVisibleCount += 1 + + # Tab must be visible + if arrowButtonVisibleCount == 0: + return True + + # If tab is less than the given offset, it must be invisible by definition + if tabPage < tabOffset: + return False + + # draw buttons + left_buttons_width = 0 + right_buttons_width = 0 + + offset = 0 + + # calculate size of the buttons on the right side + offset = self._rect.x + self._rect.width + + for i in xrange(button_count): + button = self._buttons[button_count - i - 1] + + if button.location != wx.RIGHT: + continue + if button.cur_state & AUI_BUTTON_STATE_HIDDEN: + continue + + offset -= button.rect.GetWidth() + right_buttons_width += button.rect.GetWidth() + + offset = 0 + + # calculate size of the buttons on the left side + for i in xrange(button_count): + button = self._buttons[button_count - i - 1] + + if button.location != wx.LEFT: + continue + if button.cur_state & AUI_BUTTON_STATE_HIDDEN: + continue + + offset += button.rect.GetWidth() + left_buttons_width += button.rect.GetWidth() + + offset = left_buttons_width + + if offset == 0: + offset += self._art.GetIndentSize() + + rect = wx.Rect(*self._rect) + rect.y = 0 + rect.height = self._rect.height + + # See if the given page is visible at the given tab offset (effectively scroll position) + for i in xrange(tabOffset, page_count): + + page = self._pages[i] + tab_button = self._tab_close_buttons[i] + + rect.x = offset + rect.width = self._rect.width - right_buttons_width - offset - 2 + + if rect.width <= 0: + return False # haven't found the tab, and we've run out of space, so return False + + size, x_extent = self._art.GetTabSize(dc, wnd, page.caption, page.bitmap, page.active, tab_button.cur_state, page.control) + offset += x_extent + + if i == tabPage: + + # If not all of the tab is visible, and supposing there's space to display it all, + # we could do better so we return False. + if (self._rect.width - right_buttons_width - offset - 2) <= 0 and (self._rect.width - right_buttons_width - left_buttons_width) > x_extent: + return False + else: + return True + + # Shouldn't really get here, but if it does, assume the tab is visible to prevent + # further looping in calling code. + return True + + + def MakeTabVisible(self, tabPage, win): + """ + Make the tab visible if it wasn't already. + + :param `tabPage`: the tab index; + :param `win`: an instance of `wx.Window` derived window. + """ + + dc = wx.ClientDC(win) + + if not self.IsTabVisible(tabPage, self.GetTabOffset(), dc, win): + for i in xrange(len(self._pages)): + if self.IsTabVisible(tabPage, i, dc, win): + self.SetTabOffset(i) + win.Refresh() + return + + + def TabHitTest(self, x, y): + """ + TabHitTest() tests if a tab was hit, passing the window pointer + back if that condition was fulfilled. + + :param `x`: the mouse `x` position; + :param `y`: the mouse `y` position. + """ + + if not self._rect.Contains((x,y)): + return None + + btn = self.ButtonHitTest(x, y) + if btn: + if btn in self._buttons: + return None + + for i in xrange(self._tab_offset, len(self._pages)): + page = self._pages[i] + if page.rect.Contains((x,y)): + return page.window + + return None + + + def ButtonHitTest(self, x, y): + """ + Tests if a button was hit. + + :param `x`: the mouse `x` position; + :param `y`: the mouse `y` position. + + :returns: and instance of L{AuiTabContainerButton} if a button was hit, ``None`` otherwise. + """ + + if not self._rect.Contains((x,y)): + return None + + for button in self._buttons: + if button.rect.Contains((x,y)) and \ + (button.cur_state & (AUI_BUTTON_STATE_HIDDEN|AUI_BUTTON_STATE_DISABLED)) == 0: + return button + + for button in self._tab_close_buttons: + if button.rect.Contains((x,y)) and \ + (button.cur_state & (AUI_BUTTON_STATE_HIDDEN|AUI_BUTTON_STATE_DISABLED)) == 0: + return button + + return None + + + def DoShowHide(self): + """ + This function shows the active window, then hides all of the other windows + (in that order). + """ + + pages = self.GetPages() + + # show new active page first + for page in pages: + if page.active: + page.window.Show(True) + break + + # hide all other pages + for page in pages: + if not page.active: + page.window.Show(False) + + +# ---------------------------------------------------------------------- +# -- AuiTabCtrl class implementation -- + +class AuiTabCtrl(wx.PyControl, AuiTabContainer): + """ + This is an actual `wx.Window`-derived window which can be used as a tab + control in the normal sense. + """ + + def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition, size=wx.DefaultSize, + style=wx.NO_BORDER|wx.WANTS_CHARS|wx.TAB_TRAVERSAL): + """ + Default class constructor. + Used internally, do not call it in your code! + + :param `parent`: the L{AuiTabCtrl} parent; + :param `id`: an identifier for the control: a value of -1 is taken to mean a default; + :param `pos`: the control position. A value of (-1, -1) indicates a default position, + chosen by either the windowing system or wxPython, depending on platform; + :param `size`: the control size. A value of (-1, -1) indicates a default size, + chosen by either the windowing system or wxPython, depending on platform; + :param `style`: the window style. + """ + + wx.PyControl.__init__(self, parent, id, pos, size, style, name="AuiTabCtrl") + AuiTabContainer.__init__(self, parent) + + self._click_pt = wx.Point(-1, -1) + self._is_dragging = False + self._hover_button = None + self._pressed_button = None + self._drag_image = None + self._drag_img_offset = (0, 0) + self._on_button = False + + self.Bind(wx.EVT_PAINT, self.OnPaint) + self.Bind(wx.EVT_ERASE_BACKGROUND, self.OnEraseBackground) + self.Bind(wx.EVT_SIZE, self.OnSize) + self.Bind(wx.EVT_LEFT_DOWN, self.OnLeftDown) + self.Bind(wx.EVT_LEFT_DCLICK, self.OnLeftDClick) + self.Bind(wx.EVT_LEFT_UP, self.OnLeftUp) + self.Bind(wx.EVT_MIDDLE_DOWN, self.OnMiddleDown) + self.Bind(wx.EVT_MIDDLE_UP, self.OnMiddleUp) + self.Bind(wx.EVT_RIGHT_DOWN, self.OnRightDown) + self.Bind(wx.EVT_RIGHT_UP, self.OnRightUp) + self.Bind(wx.EVT_SET_FOCUS, self.OnSetFocus) + self.Bind(wx.EVT_KILL_FOCUS, self.OnKillFocus) + self.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown) + self.Bind(wx.EVT_MOUSE_CAPTURE_LOST, self.OnCaptureLost) + self.Bind(wx.EVT_MOTION, self.OnMotion) + self.Bind(wx.EVT_LEAVE_WINDOW, self.OnLeaveWindow) + self.Bind(EVT_AUINOTEBOOK_BUTTON, self.OnButton) + + + def IsDragging(self): + """ Returns whether the user is dragging a tab with the mouse or not. """ + + return self._is_dragging + + + def GetDefaultBorder(self): + """ Returns the default border style for L{AuiTabCtrl}. """ + + return wx.BORDER_NONE + + + def OnPaint(self, event): + """ + Handles the ``wx.EVT_PAINT`` event for L{AuiTabCtrl}. + + :param `event`: a `wx.PaintEvent` event to be processed. + """ + + dc = wx.PaintDC(self) + dc.SetFont(self.GetFont()) + + if self.GetPageCount() > 0: + self.Render(dc, self) + + + def OnEraseBackground(self, event): + """ + Handles the ``wx.EVT_ERASE_BACKGROUND`` event for L{AuiTabCtrl}. + + :param `event`: a `wx.EraseEvent` event to be processed. + + :note: This is intentionally empty, to reduce flicker. + """ + + pass + + + def DoGetBestSize(self): + """ + Gets the size which best suits the window: for a control, it would be the + minimal size which doesn't truncate the control, for a panel - the same + size as it would have after a call to `Fit()`. + + :note: Overridden from `wx.PyControl`. + """ + + return wx.Size(self._rect.width, self._rect.height) + + + def OnSize(self, event): + """ + Handles the ``wx.EVT_SIZE`` event for L{AuiTabCtrl}. + + :param `event`: a `wx.SizeEvent` event to be processed. + """ + + s = event.GetSize() + self.SetTabRect(wx.Rect(0, 0, s.GetWidth(), s.GetHeight())) + + + def OnLeftDown(self, event): + """ + Handles the ``wx.EVT_LEFT_DOWN`` event for L{AuiTabCtrl}. + + :param `event`: a `wx.MouseEvent` event to be processed. + """ + + self.CaptureMouse() + self._click_pt = wx.Point(-1, -1) + self._is_dragging = False + self._click_tab = None + self._pressed_button = None + + wnd = self.TabHitTest(event.GetX(), event.GetY()) + + if wnd is not None: + new_selection = self.GetIdxFromWindow(wnd) + + # AuiNotebooks always want to receive this event + # even if the tab is already active, because they may + # have multiple tab controls + if new_selection != self.GetActivePage() or isinstance(self.GetParent(), AuiNotebook): + + e = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_PAGE_CHANGING, self.GetId()) + e.SetSelection(new_selection) + e.SetOldSelection(self.GetActivePage()) + e.SetEventObject(self) + self.GetEventHandler().ProcessEvent(e) + + self._click_pt.x = event.GetX() + self._click_pt.y = event.GetY() + self._click_tab = wnd + else: + page_index = self.GetActivePage() + if page_index != wx.NOT_FOUND: + self.GetWindowFromIdx(page_index).SetFocus() + + if self._hover_button: + self._pressed_button = self._hover_button + self._pressed_button.cur_state = AUI_BUTTON_STATE_PRESSED + self._on_button = True + self.Refresh() + self.Update() + + + def OnCaptureLost(self, event): + """ + Handles the ``wx.EVT_MOUSE_CAPTURE_LOST`` event for L{AuiTabCtrl}. + + :param `event`: a `wx.MouseCaptureLostEvent` event to be processed. + """ + + if self._is_dragging: + self._is_dragging = False + self._on_button = False + + if self._drag_image: + self._drag_image.EndDrag() + del self._drag_image + self._drag_image = None + + event = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_CANCEL_DRAG, self.GetId()) + event.SetSelection(self.GetIdxFromWindow(self._click_tab)) + event.SetOldSelection(event.GetSelection()) + event.SetEventObject(self) + self.GetEventHandler().ProcessEvent(event) + + + def OnLeftUp(self, event): + """ + Handles the ``wx.EVT_LEFT_UP`` event for L{AuiTabCtrl}. + + :param `event`: a `wx.MouseEvent` event to be processed. + """ + + self._on_button = False + + if self._is_dragging: + + if self.HasCapture(): + self.ReleaseMouse() + + self._is_dragging = False + if self._drag_image: + self._drag_image.EndDrag() + del self._drag_image + self._drag_image = None + self.GetParent().Refresh() + + evt = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_END_DRAG, self.GetId()) + evt.SetSelection(self.GetIdxFromWindow(self._click_tab)) + evt.SetOldSelection(evt.GetSelection()) + evt.SetEventObject(self) + self.GetEventHandler().ProcessEvent(evt) + + return + + self.GetParent()._mgr.HideHint() + + if self.HasCapture(): + self.ReleaseMouse() + + if self._hover_button: + self._pressed_button = self._hover_button + + if self._pressed_button: + + # make sure we're still clicking the button + button = self.ButtonHitTest(event.GetX(), event.GetY()) + + if button is None: + return + + if button != self._pressed_button: + self._pressed_button = None + return + + self.Refresh() + self.Update() + + if self._pressed_button.cur_state & AUI_BUTTON_STATE_DISABLED == 0: + + evt = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_BUTTON, self.GetId()) + evt.SetSelection(self.GetIdxFromWindow(self._click_tab)) + evt.SetInt(self._pressed_button.id) + evt.SetEventObject(self) + eventHandler = self.GetEventHandler() + + if eventHandler is not None: + eventHandler.ProcessEvent(evt) + + self._pressed_button = None + + self._click_pt = wx.Point(-1, -1) + self._is_dragging = False + self._click_tab = None + + + def OnMiddleUp(self, event): + """ + Handles the ``wx.EVT_MIDDLE_UP`` event for L{AuiTabCtrl}. + + :param `event`: a `wx.MouseEvent` event to be processed. + """ + + eventHandler = self.GetEventHandler() + if not isinstance(eventHandler, AuiTabCtrl): + event.Skip() + return + + x, y = event.GetX(), event.GetY() + wnd = self.TabHitTest(x, y) + + if wnd: + e = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_TAB_MIDDLE_UP, self.GetId()) + e.SetEventObject(self) + e.SetSelection(self.GetIdxFromWindow(wnd)) + self.GetEventHandler().ProcessEvent(e) + elif not self.ButtonHitTest(x, y): + e = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_BG_MIDDLE_UP, self.GetId()) + e.SetEventObject(self) + self.GetEventHandler().ProcessEvent(e) + + + def OnMiddleDown(self, event): + """ + Handles the ``wx.EVT_MIDDLE_DOWN`` event for L{AuiTabCtrl}. + + :param `event`: a `wx.MouseEvent` event to be processed. + """ + + eventHandler = self.GetEventHandler() + if not isinstance(eventHandler, AuiTabCtrl): + event.Skip() + return + + x, y = event.GetX(), event.GetY() + wnd = self.TabHitTest(x, y) + + if wnd: + e = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_TAB_MIDDLE_DOWN, self.GetId()) + e.SetEventObject(self) + e.SetSelection(self.GetIdxFromWindow(wnd)) + self.GetEventHandler().ProcessEvent(e) + elif not self.ButtonHitTest(x, y): + e = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_BG_MIDDLE_DOWN, self.GetId()) + e.SetEventObject(self) + self.GetEventHandler().ProcessEvent(e) + + + def OnRightUp(self, event): + """ + Handles the ``wx.EVT_RIGHT_UP`` event for L{AuiTabCtrl}. + + :param `event`: a `wx.MouseEvent` event to be processed. + """ + + x, y = event.GetX(), event.GetY() + wnd = self.TabHitTest(x, y) + + if wnd: + e = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_TAB_RIGHT_UP, self.GetId()) + e.SetEventObject(self) + e.SetSelection(self.GetIdxFromWindow(wnd)) + self.GetEventHandler().ProcessEvent(e) + elif not self.ButtonHitTest(x, y): + e = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_BG_RIGHT_UP, self.GetId()) + e.SetEventObject(self) + self.GetEventHandler().ProcessEvent(e) + + + def OnRightDown(self, event): + """ + Handles the ``wx.EVT_RIGHT_DOWN`` event for L{AuiTabCtrl}. + + :param `event`: a `wx.MouseEvent` event to be processed. + """ + + x, y = event.GetX(), event.GetY() + wnd = self.TabHitTest(x, y) + + if wnd: + e = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_TAB_RIGHT_DOWN, self.GetId()) + e.SetEventObject(self) + e.SetSelection(self.GetIdxFromWindow(wnd)) + self.GetEventHandler().ProcessEvent(e) + elif not self.ButtonHitTest(x, y): + e = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_BG_RIGHT_DOWN, self.GetId()) + e.SetEventObject(self) + self.GetEventHandler().ProcessEvent(e) + + + def OnLeftDClick(self, event): + """ + Handles the ``wx.EVT_LEFT_DCLICK`` event for L{AuiTabCtrl}. + + :param `event`: a `wx.MouseEvent` event to be processed. + """ + + x, y = event.GetX(), event.GetY() + wnd = self.TabHitTest(x, y) + + if wnd: + e = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_TAB_DCLICK, self.GetId()) + e.SetEventObject(self) + e.SetSelection(self.GetIdxFromWindow(wnd)) + self.GetEventHandler().ProcessEvent(e) + elif not self.ButtonHitTest(x, y): + e = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_BG_DCLICK, self.GetId()) + e.SetEventObject(self) + self.GetEventHandler().ProcessEvent(e) + + + def OnMotion(self, event): + """ + Handles the ``wx.EVT_MOTION`` event for L{AuiTabCtrl}. + + :param `event`: a `wx.MouseEvent` event to be processed. + """ + + pos = event.GetPosition() + + # check if the mouse is hovering above a button + + button = self.ButtonHitTest(pos.x, pos.y) + wnd = self.TabHitTest(pos.x, pos.y) + + if wnd is not None: + mouse_tab = self.GetIdxFromWindow(wnd) + if not self._pages[mouse_tab].enabled: + self._hover_button = None + return + + if self._on_button: + return + + if button: + + if self._hover_button and button != self._hover_button: + self._hover_button.cur_state = AUI_BUTTON_STATE_NORMAL + self._hover_button = None + self.Refresh() + self.Update() + + if button.cur_state != AUI_BUTTON_STATE_HOVER: + button.cur_state = AUI_BUTTON_STATE_HOVER + self.Refresh() + self.Update() + self._hover_button = button + return + + else: + + if self._hover_button: + self._hover_button.cur_state = AUI_BUTTON_STATE_NORMAL + self._hover_button = None + self.Refresh() + self.Update() + + if not event.LeftIsDown() or self._click_pt == wx.Point(-1, -1): + return + + if not self.HasCapture(): + return + + wnd = self.TabHitTest(pos.x, pos.y) + + if not self._is_dragging: + + drag_x_threshold = wx.SystemSettings.GetMetric(wx.SYS_DRAG_X) + drag_y_threshold = wx.SystemSettings.GetMetric(wx.SYS_DRAG_Y) + + if abs(pos.x - self._click_pt.x) > drag_x_threshold or \ + abs(pos.y - self._click_pt.y) > drag_y_threshold: + + self._is_dragging = True + + if self._drag_image: + self._drag_image.EndDrag() + del self._drag_image + self._drag_image = None + + if self._agwFlags & AUI_NB_DRAW_DND_TAB: + # Create the custom draw image from the icons and the text of the item + mouse_tab = self.GetIdxFromWindow(wnd) + page = self._pages[mouse_tab] + tab_button = self._tab_close_buttons[mouse_tab] + self._drag_image = TabDragImage(self, page, tab_button.cur_state, self._art) + + if self._agwFlags & AUI_NB_TAB_FLOAT: + self._drag_image.BeginDrag(wx.Point(0,0), self, fullScreen=True) + else: + self._drag_image.BeginDragBounded(wx.Point(0,0), self, self.GetParent()) + + # Capture the mouse cursor position offset relative to + # The tab image location + self._drag_img_offset = (pos[0] - page.rect.x, + pos[1] - page.rect.y) + + self._drag_image.Show() + + if not wnd: + evt2 = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_BEGIN_DRAG, self.GetId()) + evt2.SetSelection(self.GetIdxFromWindow(self._click_tab)) + evt2.SetOldSelection(evt2.GetSelection()) + evt2.SetEventObject(self) + self.GetEventHandler().ProcessEvent(evt2) + if evt2.GetDispatched(): + return + + evt3 = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_DRAG_MOTION, self.GetId()) + evt3.SetSelection(self.GetIdxFromWindow(self._click_tab)) + evt3.SetOldSelection(evt3.GetSelection()) + evt3.SetEventObject(self) + self.GetEventHandler().ProcessEvent(evt3) + + if self._drag_image: + # Apply the drag images offset + pos -= self._drag_img_offset + self._drag_image.Move(pos) + + + def OnLeaveWindow(self, event): + """ + Handles the ``wx.EVT_LEAVE_WINDOW`` event for L{AuiTabCtrl}. + + :param `event`: a `wx.MouseEvent` event to be processed. + """ + + if self._hover_button: + self._hover_button.cur_state = AUI_BUTTON_STATE_NORMAL + self._hover_button = None + self.Refresh() + self.Update() + + + def OnButton(self, event): + """ + Handles the ``EVT_AUINOTEBOOK_BUTTON`` event for L{AuiTabCtrl}. + + :param `event`: a L{AuiNotebookEvent} event to be processed. + """ + + button = event.GetInt() + + if button == AUI_BUTTON_LEFT or button == AUI_BUTTON_RIGHT: + if button == AUI_BUTTON_LEFT: + if self.GetTabOffset() > 0: + + self.SetTabOffset(self.GetTabOffset()-1) + self.Refresh() + self.Update() + else: + self.SetTabOffset(self.GetTabOffset()+1) + self.Refresh() + self.Update() + + elif button == AUI_BUTTON_WINDOWLIST: + idx = self.GetArtProvider().ShowDropDown(self, self._pages, self.GetActivePage()) + + if idx != -1: + + e = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_PAGE_CHANGING, self.GetId()) + e.SetSelection(idx) + e.SetOldSelection(self.GetActivePage()) + e.SetEventObject(self) + self.GetEventHandler().ProcessEvent(e) + + else: + event.Skip() + + + def OnSetFocus(self, event): + """ + Handles the ``wx.EVT_SET_FOCUS`` event for L{AuiTabCtrl}. + + :param `event`: a `wx.FocusEvent` event to be processed. + """ + + self.Refresh() + + + def OnKillFocus(self, event): + """ + Handles the ``wx.EVT_KILL_FOCUS`` event for L{AuiTabCtrl}. + + :param `event`: a `wx.FocusEvent` event to be processed. + """ + + self.Refresh() + + + def OnKeyDown(self, event): + """ + Handles the ``wx.EVT_KEY_DOWN`` event for L{AuiTabCtrl}. + + :param `event`: a `wx.KeyEvent` event to be processed. + """ + + key = event.GetKeyCode() + nb = self.GetParent() + + if key == wx.WXK_LEFT: + nb.AdvanceSelection(False) + self.SetFocus() + + elif key == wx.WXK_RIGHT: + nb.AdvanceSelection(True) + self.SetFocus() + + elif key == wx.WXK_HOME: + newPage = 0 + nb.SetSelection(newPage) + self.SetFocus() + + elif key == wx.WXK_END: + newPage = nb.GetPageCount() - 1 + nb.SetSelection(newPage) + self.SetFocus() + + elif key == wx.WXK_TAB: + if not event.ControlDown(): + flags = 0 + if not event.ShiftDown(): flags |= wx.NavigationKeyEvent.IsForward + if event.CmdDown(): flags |= wx.NavigationKeyEvent.WinChange + self.Navigate(flags) + else: + + if not nb or not isinstance(nb, AuiNotebook): + event.Skip() + return + + bForward = bWindowChange = 0 + if not event.ShiftDown(): bForward |= wx.NavigationKeyEvent.IsForward + if event.CmdDown(): bWindowChange |= wx.NavigationKeyEvent.WinChange + + keyEvent = wx.NavigationKeyEvent() + keyEvent.SetDirection(bForward) + keyEvent.SetWindowChange(bWindowChange) + keyEvent.SetFromTab(True) + keyEvent.SetEventObject(nb) + + if not nb.GetEventHandler().ProcessEvent(keyEvent): + + # Not processed? Do an explicit tab into the page. + win = self.GetWindowFromIdx(self.GetActivePage()) + if win: + win.SetFocus() + + self.SetFocus() + + return + + else: + event.Skip() + + + def OnKeyDown2(self, event): + """ + Deprecated. + + Handles the ``wx.EVT_KEY_DOWN`` event for L{AuiTabCtrl}. + + :param `event`: a `wx.KeyEvent` event to be processed. + + :warning: This method implementation is now deprecated. Refer to L{OnKeyDown} + for the correct one. + """ + + if self.GetActivePage() == -1: + event.Skip() + return + + # We can't leave tab processing to the system on Windows, tabs and keys + # get eaten by the system and not processed properly if we specify both + # wxTAB_TRAVERSAL and wxWANTS_CHARS. And if we specify just wxTAB_TRAVERSAL, + # we don't key arrow key events. + + key = event.GetKeyCode() + + if key == wx.WXK_NUMPAD_PAGEUP: + key = wx.WXK_PAGEUP + if key == wx.WXK_NUMPAD_PAGEDOWN: + key = wx.WXK_PAGEDOWN + if key == wx.WXK_NUMPAD_HOME: + key = wx.WXK_HOME + if key == wx.WXK_NUMPAD_END: + key = wx.WXK_END + if key == wx.WXK_NUMPAD_LEFT: + key = wx.WXK_LEFT + if key == wx.WXK_NUMPAD_RIGHT: + key = wx.WXK_RIGHT + + if key == wx.WXK_TAB or key == wx.WXK_PAGEUP or key == wx.WXK_PAGEDOWN: + + bCtrlDown = event.ControlDown() + bShiftDown = event.ShiftDown() + + bForward = (key == wx.WXK_TAB and not bShiftDown) or (key == wx.WXK_PAGEDOWN) + bWindowChange = (key == wx.WXK_PAGEUP) or (key == wx.WXK_PAGEDOWN) or bCtrlDown + bFromTab = (key == wx.WXK_TAB) + + nb = self.GetParent() + if not nb or not isinstance(nb, AuiNotebook): + event.Skip() + return + + keyEvent = wx.NavigationKeyEvent() + keyEvent.SetDirection(bForward) + keyEvent.SetWindowChange(bWindowChange) + keyEvent.SetFromTab(bFromTab) + keyEvent.SetEventObject(nb) + + if not nb.GetEventHandler().ProcessEvent(keyEvent): + + # Not processed? Do an explicit tab into the page. + win = self.GetWindowFromIdx(self.GetActivePage()) + if win: + win.SetFocus() + + return + + if len(self._pages) < 2: + event.Skip() + return + + newPage = -1 + + if self.GetLayoutDirection() == wx.Layout_RightToLeft: + forwardKey = wx.WXK_LEFT + backwardKey = wx.WXK_RIGHT + else: + forwardKey = wx.WXK_RIGHT + backwardKey = wx.WXK_LEFT + + if key == forwardKey: + if self.GetActivePage() == -1: + newPage = 0 + elif self.GetActivePage() < len(self._pages) - 1: + newPage = self.GetActivePage() + 1 + + elif key == backwardKey: + if self.GetActivePage() == -1: + newPage = len(self._pages) - 1 + elif self.GetActivePage() > 0: + newPage = self.GetActivePage() - 1 + + elif key == wx.WXK_HOME: + newPage = 0 + + elif key == wx.WXK_END: + newPage = len(self._pages) - 1 + + else: + event.Skip() + + if newPage != -1: + e = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_PAGE_CHANGING, self.GetId()) + e.SetSelection(newPage) + e.SetOldSelection(newPage) + e.SetEventObject(self) + self.GetEventHandler().ProcessEvent(e) + + else: + event.Skip() + + +# ---------------------------------------------------------------------- + +class TabFrame(wx.PyWindow): + """ + TabFrame is an interesting case. It's important that all child pages + of the multi-notebook control are all actually children of that control + (and not grandchildren). TabFrame facilitates this. There is one + instance of TabFrame for each tab control inside the multi-notebook. + + It's important to know that TabFrame is not a real window, but it merely + used to capture the dimensions/positioning of the internal tab control and + it's managed page windows. + """ + + def __init__(self, parent): + """ + Default class constructor. + Used internally, do not call it in your code! + """ + + pre = wx.PrePyWindow() + + self._tabs = None + self._rect = wx.Rect(0, 0, 200, 200) + self._tab_ctrl_height = 20 + self._tab_rect = wx.Rect() + self._parent = parent + + self.PostCreate(pre) + + + def SetTabCtrlHeight(self, h): + """ + Sets the tab control height. + + :param `h`: the tab area height. + """ + + self._tab_ctrl_height = h + + + def DoSetSize(self, x, y, width, height, flags=wx.SIZE_AUTO): + """ + Sets the position and size of the window in pixels. The `flags` + parameter indicates the interpretation of the other params if they are + equal to -1. + + :param `x`: the window `x` position; + :param `y`: the window `y` position; + :param `width`: the window width; + :param `height`: the window height; + :param `flags`: may have one of this bit set: + + =================================== ====================================== + Size Flags Description + =================================== ====================================== + ``wx.SIZE_AUTO`` A -1 indicates that a class-specific default should be used. + ``wx.SIZE_AUTO_WIDTH`` A -1 indicates that a class-specific default should be used for the width. + ``wx.SIZE_AUTO_HEIGHT`` A -1 indicates that a class-specific default should be used for the height. + ``wx.SIZE_USE_EXISTING`` Existing dimensions should be used if -1 values are supplied. + ``wx.SIZE_ALLOW_MINUS_ONE`` Allow dimensions of -1 and less to be interpreted as real dimensions, not default values. + ``wx.SIZE_FORCE`` Normally, if the position and the size of the window are already the same as the parameters of this function, nothing is done. but with this flag a window resize may be forced even in this case (supported in wx 2.6.2 and later and only implemented for MSW and ignored elsewhere currently) + =================================== ====================================== + + :note: Overridden from `wx.PyControl`. + """ + + self._rect = wx.Rect(x, y, max(1, width), max(1, height)) + self.DoSizing() + + + def DoGetSize(self): + """ + Returns the window size. + + :note: Overridden from `wx.PyControl`. + """ + + return self._rect.width, self._rect.height + + + def DoGetClientSize(self): + """ + Returns the window client size. + + :note: Overridden from `wx.PyControl`. + """ + + return self._rect.width, self._rect.height + + + def Show(self, show=True): + """ + Shows/hides the window. + + :param `show`: ``True`` to show the window, ``False`` otherwise. + + :note: Overridden from `wx.PyControl`, this method always returns ``False`` as + L{TabFrame} should never be phisically shown on screen. + """ + + return False + + + def DoSizing(self): + """ Does the actual sizing of the tab control. """ + + if not self._tabs: + return + + hideOnSingle = ((self._tabs.GetAGWFlags() & AUI_NB_HIDE_ON_SINGLE_TAB) and \ + self._tabs.GetPageCount() <= 1) + + if not hideOnSingle and not self._parent._hide_tabs: + tab_height = self._tab_ctrl_height + + self._tab_rect = wx.Rect(self._rect.x, self._rect.y, self._rect.width, self._tab_ctrl_height) + + if self._tabs.GetAGWFlags() & AUI_NB_BOTTOM: + self._tab_rect = wx.Rect(self._rect.x, self._rect.y + self._rect.height - tab_height, + self._rect.width, tab_height) + self._tabs.SetDimensions(self._rect.x, self._rect.y + self._rect.height - tab_height, + self._rect.width, tab_height) + self._tabs.SetTabRect(wx.Rect(0, 0, self._rect.width, tab_height)) + + else: + + self._tab_rect = wx.Rect(self._rect.x, self._rect.y, self._rect.width, tab_height) + self._tabs.SetDimensions(self._rect.x, self._rect.y, self._rect.width, tab_height) + self._tabs.SetTabRect(wx.Rect(0, 0, self._rect.width, tab_height)) + + # TODO: elif (GetAGWFlags() & AUI_NB_LEFT) + # TODO: elif (GetAGWFlags() & AUI_NB_RIGHT) + + self._tabs.Refresh() + self._tabs.Update() + + else: + + tab_height = 0 + self._tabs.SetDimensions(self._rect.x, self._rect.y, self._rect.width, tab_height) + self._tabs.SetTabRect(wx.Rect(0, 0, self._rect.width, tab_height)) + + pages = self._tabs.GetPages() + + for page in pages: + + height = self._rect.height - tab_height + + if height < 0: + # avoid passing negative height to wx.Window.SetSize(), this + # results in assert failures/GTK+ warnings + height = 0 + + if self._tabs.GetAGWFlags() & AUI_NB_BOTTOM: + page.window.SetDimensions(self._rect.x, self._rect.y, self._rect.width, height) + + else: + page.window.SetDimensions(self._rect.x, self._rect.y + tab_height, + self._rect.width, height) + + # TODO: elif (GetAGWFlags() & AUI_NB_LEFT) + # TODO: elif (GetAGWFlags() & AUI_NB_RIGHT) + + if repr(page.window.__class__).find("AuiMDIChildFrame") >= 0: + page.window.ApplyMDIChildFrameRect() + + + def Update(self): + """ + Calling this method immediately repaints the invalidated area of the window + and all of its children recursively while this would usually only happen when + the flow of control returns to the event loop. + + :note: Notice that this function doesn't invalidate any area of the window so + nothing happens if nothing has been invalidated (i.e. marked as requiring a redraw). + Use `Refresh` first if you want to immediately redraw the window unconditionally. + + :note: Overridden from `wx.PyControl`. + """ + + # does nothing + pass + + +# ---------------------------------------------------------------------- +# -- AuiNotebook class implementation -- + +class AuiNotebook(wx.PyPanel): + """ + AuiNotebook is a notebook control which implements many features common in + applications with dockable panes. Specifically, AuiNotebook implements functionality + which allows the user to rearrange tab order via drag-and-drop, split the tab window + into many different splitter configurations, and toggle through different themes to + customize the control's look and feel. + + An effort has been made to try to maintain an API as similar to that of `wx.Notebook`. + + The default theme that is used is L{AuiDefaultTabArt}, which provides a modern, glossy + look and feel. The theme can be changed by calling L{AuiNotebook.SetArtProvider}. + """ + + def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition, size=wx.DefaultSize, + style=0, agwStyle=AUI_NB_DEFAULT_STYLE): + """ + Default class constructor. + + :param `parent`: the L{AuiNotebook} parent; + :param `id`: an identifier for the control: a value of -1 is taken to mean a default; + :param `pos`: the control position. A value of (-1, -1) indicates a default position, + chosen by either the windowing system or wxPython, depending on platform; + :param `size`: the control size. A value of (-1, -1) indicates a default size, + chosen by either the windowing system or wxPython, depending on platform; + :param `style`: the underlying `wx.PyPanel` window style; + :param `agwStyle`: the AGW-specific window style. This can be a combination of the following bits: + + ==================================== ================================== + Flag name Description + ==================================== ================================== + ``AUI_NB_TOP`` With this style, tabs are drawn along the top of the notebook + ``AUI_NB_LEFT`` With this style, tabs are drawn along the left of the notebook. Not implemented yet. + ``AUI_NB_RIGHT`` With this style, tabs are drawn along the right of the notebook. Not implemented yet. + ``AUI_NB_BOTTOM`` With this style, tabs are drawn along the bottom of the notebook + ``AUI_NB_TAB_SPLIT`` Allows the tab control to be split by dragging a tab + ``AUI_NB_TAB_MOVE`` Allows a tab to be moved horizontally by dragging + ``AUI_NB_TAB_EXTERNAL_MOVE`` Allows a tab to be moved to another tab control + ``AUI_NB_TAB_FIXED_WIDTH`` With this style, all tabs have the same width + ``AUI_NB_SCROLL_BUTTONS`` With this style, left and right scroll buttons are displayed + ``AUI_NB_WINDOWLIST_BUTTON`` With this style, a drop-down list of windows is available + ``AUI_NB_CLOSE_BUTTON`` With this style, a close button is available on the tab bar + ``AUI_NB_CLOSE_ON_ACTIVE_TAB`` With this style, a close button is available on the active tab + ``AUI_NB_CLOSE_ON_ALL_TABS`` With this style, a close button is available on all tabs + ``AUI_NB_MIDDLE_CLICK_CLOSE`` Allows to close L{AuiNotebook} tabs by mouse middle button click + ``AUI_NB_SUB_NOTEBOOK`` This style is used by L{AuiManager} to create automatic AuiNotebooks + ``AUI_NB_HIDE_ON_SINGLE_TAB`` Hides the tab window if only one tab is present + ``AUI_NB_SMART_TABS`` Use Smart Tabbing, like ``Alt`` + ``Tab`` on Windows + ``AUI_NB_USE_IMAGES_DROPDOWN`` Uses images on dropdown window list menu instead of check items + ``AUI_NB_CLOSE_ON_TAB_LEFT`` Draws the tab close button on the left instead of on the right (a la Camino browser) + ``AUI_NB_TAB_FLOAT`` Allows the floating of single tabs. Known limitation: when the notebook is more or less full screen, tabs cannot be dragged far enough outside of the notebook to become floating pages + ``AUI_NB_DRAW_DND_TAB`` Draws an image representation of a tab while dragging (on by default) + ``AUI_NB_ORDER_BY_ACCESS`` Tab navigation order by last access time for the tabs + ``AUI_NB_NO_TAB_FOCUS`` Don't draw tab focus rectangle + ==================================== ================================== + + Default value for `agwStyle` is: + ``AUI_NB_DEFAULT_STYLE`` = ``AUI_NB_TOP`` | ``AUI_NB_TAB_SPLIT`` | ``AUI_NB_TAB_MOVE`` | ``AUI_NB_SCROLL_BUTTONS`` | ``AUI_NB_CLOSE_ON_ACTIVE_TAB`` | ``AUI_NB_MIDDLE_CLICK_CLOSE`` | ``AUI_NB_DRAW_DND_TAB`` + + """ + + self._curpage = -1 + self._tab_id_counter = AuiBaseTabCtrlId + self._dummy_wnd = None + self._hide_tabs = False + self._sash_dclick_unsplit = False + self._tab_ctrl_height = 20 + self._requested_bmp_size = wx.Size(-1, -1) + self._requested_tabctrl_height = -1 + self._textCtrl = None + self._tabBounds = (-1, -1) + + wx.PyPanel.__init__(self, parent, id, pos, size, style|wx.BORDER_NONE|wx.TAB_TRAVERSAL) + self._mgr = framemanager.AuiManager() + self._tabs = AuiTabContainer(self) + + self.InitNotebook(agwStyle) + + + def GetTabContainer(self): + """ Returns the instance of L{AuiTabContainer}. """ + + return self._tabs + + + def InitNotebook(self, agwStyle): + """ + Contains common initialization code called by all constructors. + + :param `agwStyle`: the notebook style. + + :see: L{__init__} + """ + + self.SetName("AuiNotebook") + self._agwFlags = agwStyle + + self._popupWin = None + self._naviIcon = None + self._imageList = None + self._last_drag_x = 0 + + self._normal_font = wx.SystemSettings.GetFont(wx.SYS_DEFAULT_GUI_FONT) + self._selected_font = wx.SystemSettings.GetFont(wx.SYS_DEFAULT_GUI_FONT) + self._selected_font.SetWeight(wx.BOLD) + + self.SetArtProvider(TA.AuiDefaultTabArt()) + + self._dummy_wnd = wx.Window(self, wx.ID_ANY, wx.Point(0, 0), wx.Size(0, 0)) + self._dummy_wnd.SetSize((200, 200)) + self._dummy_wnd.Show(False) + + self._mgr.SetManagedWindow(self) + self._mgr.SetAGWFlags(AUI_MGR_DEFAULT) + self._mgr.SetDockSizeConstraint(1.0, 1.0) # no dock size constraint + + self._mgr.AddPane(self._dummy_wnd, framemanager.AuiPaneInfo().Name("dummy").Bottom().CaptionVisible(False).Show(False)) + self._mgr.Update() + + self.Bind(wx.EVT_SIZE, self.OnSize) + self.Bind(wx.EVT_CHILD_FOCUS, self.OnChildFocusNotebook) + self.Bind(EVT_AUINOTEBOOK_PAGE_CHANGING, self.OnTabClicked, + id=AuiBaseTabCtrlId, id2=AuiBaseTabCtrlId+500) + self.Bind(EVT_AUINOTEBOOK_BEGIN_DRAG, self.OnTabBeginDrag, + id=AuiBaseTabCtrlId, id2=AuiBaseTabCtrlId+500) + self.Bind(EVT_AUINOTEBOOK_END_DRAG, self.OnTabEndDrag, + id=AuiBaseTabCtrlId, id2=AuiBaseTabCtrlId+500) + self.Bind(EVT_AUINOTEBOOK_DRAG_MOTION, self.OnTabDragMotion, + id=AuiBaseTabCtrlId, id2=AuiBaseTabCtrlId+500) + self.Bind(EVT_AUINOTEBOOK_CANCEL_DRAG, self.OnTabCancelDrag, + id=AuiBaseTabCtrlId, id2=AuiBaseTabCtrlId+500) + self.Bind(EVT_AUINOTEBOOK_BUTTON, self.OnTabButton, + id=AuiBaseTabCtrlId, id2=AuiBaseTabCtrlId+500) + self.Bind(EVT_AUINOTEBOOK_TAB_MIDDLE_DOWN, self.OnTabMiddleDown, + id=AuiBaseTabCtrlId, id2=AuiBaseTabCtrlId+500) + self.Bind(EVT_AUINOTEBOOK_TAB_MIDDLE_UP, self.OnTabMiddleUp, + id=AuiBaseTabCtrlId, id2=AuiBaseTabCtrlId+500) + self.Bind(EVT_AUINOTEBOOK_TAB_RIGHT_DOWN, self.OnTabRightDown, + id=AuiBaseTabCtrlId, id2=AuiBaseTabCtrlId+500) + self.Bind(EVT_AUINOTEBOOK_TAB_RIGHT_UP, self.OnTabRightUp, + id=AuiBaseTabCtrlId, id2=AuiBaseTabCtrlId+500) + self.Bind(EVT_AUINOTEBOOK_BG_DCLICK, self.OnTabBgDClick, + id=AuiBaseTabCtrlId, id2=AuiBaseTabCtrlId+500) + self.Bind(EVT_AUINOTEBOOK_TAB_DCLICK, self.OnTabDClick, + id=AuiBaseTabCtrlId, id2=AuiBaseTabCtrlId+500) + + self.Bind(wx.EVT_NAVIGATION_KEY, self.OnNavigationKeyNotebook) + + + def SetArtProvider(self, art): + """ + Sets the art provider to be used by the notebook. + + :param `art`: an art provider. + """ + + self._tabs.SetArtProvider(art) + self.UpdateTabCtrlHeight(force=True) + + + def SavePerspective(self): + """ + Saves the entire user interface layout into an encoded string, which can then + be stored by the application (probably using `wx.Config`). When a perspective + is restored using L{LoadPerspective}, the entire user interface will return + to the state it was when the perspective was saved. + """ + + # Build list of panes/tabs + tabs = "" + all_panes = self._mgr.GetAllPanes() + + for pane in all_panes: + + if pane.name == "dummy": + continue + + tabframe = pane.window + + if tabs: + tabs += "|" + + tabs += pane.name + "=" + + # add tab id's + page_count = tabframe._tabs.GetPageCount() + + for p in xrange(page_count): + + page = tabframe._tabs.GetPage(p) + page_idx = self._tabs.GetIdxFromWindow(page.window) + + if p: + tabs += "," + + if p == tabframe._tabs.GetActivePage(): + tabs += "+" + elif page_idx == self._curpage: + tabs += "*" + + tabs += "%u"%page_idx + + tabs += "@" + + # Add frame perspective + tabs += self._mgr.SavePerspective() + + return tabs + + + def LoadPerspective(self, layout): + """ + Loads a layout which was saved with L{SavePerspective}. + + :param `layout`: a string which contains a saved L{AuiNotebook} layout. + """ + + # Remove all tab ctrls (but still keep them in main index) + tab_count = self._tabs.GetPageCount() + for i in xrange(tab_count): + wnd = self._tabs.GetWindowFromIdx(i) + + # find out which onscreen tab ctrl owns this tab + ctrl, ctrl_idx = self.FindTab(wnd) + if not ctrl: + return False + + # remove the tab from ctrl + if not ctrl.RemovePage(wnd): + return False + + self.RemoveEmptyTabFrames() + + sel_page = 0 + tabs = layout[0:layout.index("@")] + to_break1 = False + + while 1: + + if "|" not in tabs: + to_break1 = True + tab_part = tabs + else: + tab_part = tabs[0:tabs.index('|')] + + if "=" not in tab_part: + # No pages in this perspective... + return False + + # Get pane name + pane_name = tab_part[0:tab_part.index("=")] + + # create a new tab frame + new_tabs = TabFrame(self) + self._tab_id_counter += 1 + new_tabs._tabs = AuiTabCtrl(self, self._tab_id_counter) + new_tabs._tabs.SetArtProvider(self._tabs.GetArtProvider().Clone()) + new_tabs.SetTabCtrlHeight(self._tab_ctrl_height) + new_tabs._tabs.SetAGWFlags(self._agwFlags) + dest_tabs = new_tabs._tabs + + # create a pane info structure with the information + # about where the pane should be added + pane_info = framemanager.AuiPaneInfo().Name(pane_name).Bottom().CaptionVisible(False) + self._mgr.AddPane(new_tabs, pane_info) + + # Get list of tab id's and move them to pane + tab_list = tab_part[tab_part.index("=")+1:] + to_break2, active_found = False, False + + while 1: + if "," not in tab_list: + to_break2 = True + tab = tab_list + else: + tab = tab_list[0:tab_list.index(",")] + tab_list = tab_list[tab_list.index(",")+1:] + + # Check if this page has an 'active' marker + c = tab[0] + if c in ['+', '*']: + tab = tab[1:] + + tab_idx = int(tab) + if tab_idx >= self.GetPageCount(): + to_break1 = True + break + + # Move tab to pane + page = self._tabs.GetPage(tab_idx) + newpage_idx = dest_tabs.GetPageCount() + dest_tabs.InsertPage(page.window, page, newpage_idx) + + if c == '+': + dest_tabs.SetActivePage(newpage_idx) + active_found = True + elif c == '*': + sel_page = tab_idx + + if to_break2: + break + + if not active_found: + dest_tabs.SetActivePage(0) + + new_tabs.DoSizing() + dest_tabs.DoShowHide() + dest_tabs.Refresh() + + if to_break1: + break + + tabs = tabs[tabs.index('|')+1:] + + # Load the frame perspective + frames = layout[layout.index('@')+1:] + self._mgr.LoadPerspective(frames) + + # Force refresh of selection + self._curpage = -1 + self.SetSelection(sel_page) + + return True + + + def SetTabCtrlHeight(self, height): + """ + Sets the tab height. + + By default, the tab control height is calculated by measuring the text + height and bitmap sizes on the tab captions. + + Calling this method will override that calculation and set the tab control + to the specified height parameter. A call to this method will override + any call to L{SetUniformBitmapSize}. Specifying -1 as the height will + return the control to its default auto-sizing behaviour. + + :param `height`: the tab control area height. + """ + + self._requested_tabctrl_height = height + + # if window is already initialized, recalculate the tab height + if self._dummy_wnd: + self.UpdateTabCtrlHeight() + + + def SetUniformBitmapSize(self, size): + """ + Ensures that all tabs will have the same height, even if some tabs + don't have bitmaps. Passing ``wx.DefaultSize`` to this + function will instruct the control to use dynamic tab height, which is + the default behaviour. Under the default behaviour, when a tab with a + large bitmap is added, the tab control's height will automatically + increase to accommodate the larger bitmap. + + :param `size`: an instance of `wx.Size` specifying the tab bitmap size. + """ + + self._requested_bmp_size = wx.Size(*size) + + # if window is already initialized, recalculate the tab height + if self._dummy_wnd: + self.UpdateTabCtrlHeight() + + + def UpdateTabCtrlHeight(self, force=False): + """ + UpdateTabCtrlHeight() does the actual tab resizing. It's meant + to be used interally. + + :param `force`: ``True`` to force the tab art to repaint. + """ + + # get the tab ctrl height we will use + height = self.CalculateTabCtrlHeight() + + # if the tab control height needs to change, update + # all of our tab controls with the new height + if self._tab_ctrl_height != height or force: + art = self._tabs.GetArtProvider() + + self._tab_ctrl_height = height + + all_panes = self._mgr.GetAllPanes() + for pane in all_panes: + + if pane.name == "dummy": + continue + + tab_frame = pane.window + tabctrl = tab_frame._tabs + tab_frame.SetTabCtrlHeight(self._tab_ctrl_height) + tabctrl.SetArtProvider(art.Clone()) + tab_frame.DoSizing() + + + def UpdateHintWindowSize(self): + """ Updates the L{AuiManager} hint window size. """ + + size = self.CalculateNewSplitSize() + + # the placeholder hint window should be set to this size + info = self._mgr.GetPane("dummy") + + if info.IsOk(): + info.MinSize(size) + info.BestSize(size) + self._dummy_wnd.SetSize(size) + + + def CalculateNewSplitSize(self): + """ Calculates the size of the new split. """ + + # count number of tab controls + tab_ctrl_count = 0 + all_panes = self._mgr.GetAllPanes() + + for pane in all_panes: + if pane.name == "dummy": + continue + + tab_ctrl_count += 1 + + # if there is only one tab control, the first split + # should happen around the middle + if tab_ctrl_count < 2: + new_split_size = self.GetClientSize() + new_split_size.x /= 2 + new_split_size.y /= 2 + + else: + + # this is in place of a more complicated calculation + # that needs to be implemented + new_split_size = wx.Size(180, 180) + + return new_split_size + + + def CalculateTabCtrlHeight(self): + """ Calculates the tab control area height. """ + + # if a fixed tab ctrl height is specified, + # just return that instead of calculating a + # tab height + if self._requested_tabctrl_height != -1: + return self._requested_tabctrl_height + + # find out new best tab height + art = self._tabs.GetArtProvider() + + return art.GetBestTabCtrlSize(self, self._tabs.GetPages(), self._requested_bmp_size) + + + def GetArtProvider(self): + """ Returns the associated art provider. """ + + return self._tabs.GetArtProvider() + + + def SetAGWWindowStyleFlag(self, agwStyle): + """ + Sets the AGW-specific style of the window. + + :param `agwStyle`: the new window style. This can be a combination of the following bits: + + ==================================== ================================== + Flag name Description + ==================================== ================================== + ``AUI_NB_TOP`` With this style, tabs are drawn along the top of the notebook + ``AUI_NB_LEFT`` With this style, tabs are drawn along the left of the notebook. Not implemented yet. + ``AUI_NB_RIGHT`` With this style, tabs are drawn along the right of the notebook. Not implemented yet. + ``AUI_NB_BOTTOM`` With this style, tabs are drawn along the bottom of the notebook + ``AUI_NB_TAB_SPLIT`` Allows the tab control to be split by dragging a tab + ``AUI_NB_TAB_MOVE`` Allows a tab to be moved horizontally by dragging + ``AUI_NB_TAB_EXTERNAL_MOVE`` Allows a tab to be moved to another tab control + ``AUI_NB_TAB_FIXED_WIDTH`` With this style, all tabs have the same width + ``AUI_NB_SCROLL_BUTTONS`` With this style, left and right scroll buttons are displayed + ``AUI_NB_WINDOWLIST_BUTTON`` With this style, a drop-down list of windows is available + ``AUI_NB_CLOSE_BUTTON`` With this style, a close button is available on the tab bar + ``AUI_NB_CLOSE_ON_ACTIVE_TAB`` With this style, a close button is available on the active tab + ``AUI_NB_CLOSE_ON_ALL_TABS`` With this style, a close button is available on all tabs + ``AUI_NB_MIDDLE_CLICK_CLOSE`` Allows to close L{AuiNotebook} tabs by mouse middle button click + ``AUI_NB_SUB_NOTEBOOK`` This style is used by L{AuiManager} to create automatic AuiNotebooks + ``AUI_NB_HIDE_ON_SINGLE_TAB`` Hides the tab window if only one tab is present + ``AUI_NB_SMART_TABS`` Use Smart Tabbing, like ``Alt`` + ``Tab`` on Windows + ``AUI_NB_USE_IMAGES_DROPDOWN`` Uses images on dropdown window list menu instead of check items + ``AUI_NB_CLOSE_ON_TAB_LEFT`` Draws the tab close button on the left instead of on the right (a la Camino browser) + ``AUI_NB_TAB_FLOAT`` Allows the floating of single tabs. Known limitation: when the notebook is more or less full screen, tabs cannot be dragged far enough outside of the notebook to become floating pages + ``AUI_NB_DRAW_DND_TAB`` Draws an image representation of a tab while dragging (on by default) + ``AUI_NB_ORDER_BY_ACCESS`` Tab navigation order by last access time for the tabs + ``AUI_NB_NO_TAB_FOCUS`` Don't draw tab focus rectangle + ==================================== ================================== + + :note: Please note that some styles cannot be changed after the window + creation and that `Refresh` might need to be be called after changing the + others for the change to take place immediately. + + :todo: Implementation of flags ``AUI_NB_RIGHT`` and ``AUI_NB_LEFT``. + """ + + self._agwFlags = agwStyle + + # if the control is already initialized + if self._mgr.GetManagedWindow() == self: + + # let all of the tab children know about the new style + + all_panes = self._mgr.GetAllPanes() + for pane in all_panes: + if pane.name == "dummy": + continue + + tabframe = pane.window + tabctrl = tabframe._tabs + tabctrl.SetAGWFlags(self._agwFlags) + tabframe.DoSizing() + tabctrl.Refresh() + tabctrl.Update() + + + def GetAGWWindowStyleFlag(self): + """ + Returns the AGW-specific style of the window. + + :see: L{SetAGWWindowStyleFlag} for a list of possible AGW-specific window styles. + """ + + return self._agwFlags + + + def AddPage(self, page, caption, select=False, bitmap=wx.NullBitmap, disabled_bitmap=wx.NullBitmap, control=None): + """ + Adds a page. If the `select` parameter is ``True``, calling this will generate a + page change event. + + :param `page`: the page to be added; + :param `caption`: specifies the text for the new page; + :param `select`: specifies whether the page should be selected; + :param `bitmap`: the `wx.Bitmap` to display in the enabled tab; + :param `disabled_bitmap`: the `wx.Bitmap` to display in the disabled tab; + :param `control`: a `wx.Window` instance inside a tab (or ``None``). + """ + + return self.InsertPage(self.GetPageCount(), page, caption, select, bitmap, disabled_bitmap, control) + + + def InsertPage(self, page_idx, page, caption, select=False, bitmap=wx.NullBitmap, disabled_bitmap=wx.NullBitmap, + control=None): + """ + This is similar to L{AddPage}, but allows the ability to specify the insert location. + + :param `page_idx`: specifies the position for the new page; + :param `page`: the page to be added; + :param `caption`: specifies the text for the new page; + :param `select`: specifies whether the page should be selected; + :param `bitmap`: the `wx.Bitmap` to display in the enabled tab; + :param `disabled_bitmap`: the `wx.Bitmap` to display in the disabled tab; + :param `control`: a `wx.Window` instance inside a tab (or ``None``). + """ + + if not page: + return False + + page.Reparent(self) + info = AuiNotebookPage() + info.window = page + info.caption = caption + info.bitmap = bitmap + info.active = False + info.control = control + + originalPaneMgr = framemanager.GetManager(page) + if originalPaneMgr: + originalPane = originalPaneMgr.GetPane(page) + + if originalPane: + info.hasCloseButton = originalPane.HasCloseButton() + + if bitmap.IsOk() and not disabled_bitmap.IsOk(): + disabled_bitmap = MakeDisabledBitmap(bitmap) + info.dis_bitmap = disabled_bitmap + + # if there are currently no tabs, the first added + # tab must be active + if self._tabs.GetPageCount() == 0: + info.active = True + + self._tabs.InsertPage(page, info, page_idx) + + # if that was the first page added, even if + # select is False, it must become the "current page" + # (though no select events will be fired) + if not select and self._tabs.GetPageCount() == 1: + select = True + + active_tabctrl = self.GetActiveTabCtrl() + if page_idx >= active_tabctrl.GetPageCount(): + active_tabctrl.AddPage(page, info) + else: + active_tabctrl.InsertPage(page, info, page_idx) + + force = False + if control: + force = True + control.Reparent(active_tabctrl) + control.Show() + + self.UpdateTabCtrlHeight(force=force) + self.DoSizing() + active_tabctrl.DoShowHide() + + # adjust selected index + if self._curpage >= page_idx: + self._curpage += 1 + + if select: + self.SetSelectionToWindow(page) + + return True + + + def DeletePage(self, page_idx): + """ + Deletes a page at the given index. Calling this method will generate a page + change event. + + :param `page_idx`: the page index to be deleted. + + :note: L{DeletePage} removes a tab from the multi-notebook, and destroys the window as well. + + :see: L{RemovePage} + """ + + if page_idx >= self._tabs.GetPageCount(): + return False + + wnd = self._tabs.GetWindowFromIdx(page_idx) + # hide the window in advance, as this will + # prevent flicker + wnd.Show(False) + + self.RemoveControlFromPage(page_idx) + + if not self.RemovePage(page_idx): + return False + + wnd.Destroy() + + return True + + + def RemovePage(self, page_idx): + """ + Removes a page, without deleting the window pointer. + + :param `page_idx`: the page index to be removed. + + :note: L{RemovePage} removes a tab from the multi-notebook, but does not destroy the window. + + :see: L{DeletePage} + """ + + # save active window pointer + active_wnd = None + if self._curpage >= 0: + active_wnd = self._tabs.GetWindowFromIdx(self._curpage) + + # save pointer of window being deleted + wnd = self._tabs.GetWindowFromIdx(page_idx) + new_active = None + + # make sure we found the page + if not wnd: + return False + + # find out which onscreen tab ctrl owns this tab + ctrl, ctrl_idx = self.FindTab(wnd) + if not ctrl: + return False + + currentPage = ctrl.GetPage(ctrl_idx) + is_curpage = (self._curpage == page_idx) + is_active_in_split = currentPage.active + + # remove the tab from main catalog + if not self._tabs.RemovePage(wnd): + return False + + # remove the tab from the onscreen tab ctrl + ctrl.RemovePage(wnd) + + if is_active_in_split: + + ctrl_new_page_count = ctrl.GetPageCount() + + if ctrl_idx >= ctrl_new_page_count: + ctrl_idx = ctrl_new_page_count - 1 + + if ctrl_idx >= 0 and ctrl_idx < ctrl.GetPageCount(): + + ctrl_idx = self.FindNextActiveTab(ctrl_idx, ctrl) + + # set new page as active in the tab split + ctrl.SetActivePage(ctrl_idx) + + # if the page deleted was the current page for the + # entire tab control, then record the window + # pointer of the new active page for activation + if is_curpage: + new_active = ctrl.GetWindowFromIdx(ctrl_idx) + + else: + + # we are not deleting the active page, so keep it the same + new_active = active_wnd + + if not new_active: + + # we haven't yet found a new page to active, + # so select the next page from the main tab + # catalogue + + if 0 <= page_idx < self._tabs.GetPageCount(): + new_active = self._tabs.GetPage(page_idx).window + if not new_active and self._tabs.GetPageCount() > 0: + new_active = self._tabs.GetPage(0).window + + self.RemoveEmptyTabFrames() + + # set new active pane + if new_active: + if not self.IsBeingDeleted(): + self._curpage = -1 + self.SetSelectionToWindow(new_active) + else: + self._curpage = -1 + self._tabs.SetNoneActive() + + return True + + + def FindNextActiveTab(self, ctrl_idx, ctrl): + """ + Finds the next active tab (used mainly when L{AuiNotebook} has inactive/disabled + tabs in it). + + :param `ctrl_idx`: the index of the first (most obvious) tab to check for active status; + :param `ctrl`: an instance of L{AuiTabCtrl}. + """ + + if self.GetEnabled(ctrl_idx): + return ctrl_idx + + for indx in xrange(ctrl_idx, ctrl.GetPageCount()): + if self.GetEnabled(indx): + return indx + + for indx in xrange(ctrl_idx, -1, -1): + if self.GetEnabled(indx): + return indx + + return 0 + + + def HideAllTabs(self, hidden=True): + """ + Hides all tabs on the L{AuiNotebook} control. + + :param `hidden`: if ``True`` hides all tabs. + """ + + self._hide_tabs = hidden + + + def SetSashDClickUnsplit(self, unsplit=True): + """ + Sets whether to unsplit a splitted L{AuiNotebook} when double-clicking on a sash. + + :param `unsplit`: ``True`` to unsplit on sash double-clicking, ``False`` otherwise. + """ + + self._sash_dclick_unsplit = unsplit + + + def GetSashDClickUnsplit(self): + """ + Returns whether a splitted L{AuiNotebook} can be unsplitted by double-clicking + on the splitter sash. + """ + + return self._sash_dclick_unsplit + + + def SetMinMaxTabWidth(self, minTabWidth, maxTabWidth): + """ + Sets the minimum and/or the maximum tab widths for L{AuiNotebook} when the + ``AUI_NB_TAB_FIXED_WIDTH`` style is defined. + + Pass -1 to either `minTabWidth` or `maxTabWidth` to reset to the default tab + width behaviour for L{AuiNotebook}. + + :param `minTabWidth`: the minimum allowed tab width, in pixels; + :param `maxTabWidth`: the maximum allowed tab width, in pixels. + + :note: Minimum and maximum tabs widths are used only when the ``AUI_NB_TAB_FIXED_WIDTH`` + style is present. + """ + + if minTabWidth > maxTabWidth: + raise Exception("Minimum tab width must be less or equal than maximum tab width") + + self._tabBounds = (minTabWidth, maxTabWidth) + self.SetAGWWindowStyleFlag(self._agwFlags) + + + def GetMinMaxTabWidth(self): + """ + Returns the minimum and the maximum tab widths for L{AuiNotebook} when the + ``AUI_NB_TAB_FIXED_WIDTH`` style is defined. + + :note: Minimum and maximum tabs widths are used only when the ``AUI_NB_TAB_FIXED_WIDTH`` + style is present. + + :see: L{SetMinMaxTabWidth} for more information. + """ + + return self._tabBounds + + + def GetPageIndex(self, page_wnd): + """ + Returns the page index for the specified window. If the window is not + found in the notebook, ``wx.NOT_FOUND`` is returned. + """ + + return self._tabs.GetIdxFromWindow(page_wnd) + + + def SetPageText(self, page_idx, text): + """ + Sets the tab label for the page. + + :param `page_idx`: the page index; + :param `text`: the new tab label. + """ + + if page_idx >= self._tabs.GetPageCount(): + return False + + # update our own tab catalog + page_info = self._tabs.GetPage(page_idx) + should_refresh = page_info.caption != text + page_info.caption = text + + # update what's on screen + ctrl, ctrl_idx = self.FindTab(page_info.window) + if not ctrl: + return False + + info = ctrl.GetPage(ctrl_idx) + should_refresh = should_refresh or info.caption != text + info.caption = text + + if should_refresh: + ctrl.Refresh() + ctrl.Update() + + self.UpdateTabCtrlHeight(force=True) + + return True + + + def GetPageText(self, page_idx): + """ + Returns the tab label for the page. + + :param `page_idx`: the page index. + """ + + if page_idx >= self._tabs.GetPageCount(): + return "" + + # update our own tab catalog + page_info = self._tabs.GetPage(page_idx) + return page_info.caption + + + def SetPageBitmap(self, page_idx, bitmap): + """ + Sets the tab bitmap for the page. + + :param `page_idx`: the page index; + :param `bitmap`: an instance of `wx.Bitmap`. + """ + + if page_idx >= self._tabs.GetPageCount(): + return False + + # update our own tab catalog + page_info = self._tabs.GetPage(page_idx) + should_refresh = page_info.bitmap is not bitmap + page_info.bitmap = bitmap + if bitmap.IsOk() and not page_info.dis_bitmap.IsOk(): + page_info.dis_bitmap = MakeDisabledBitmap(bitmap) + + # tab height might have changed + self.UpdateTabCtrlHeight() + + # update what's on screen + ctrl, ctrl_idx = self.FindTab(page_info.window) + if not ctrl: + return False + + info = ctrl.GetPage(ctrl_idx) + should_refresh = should_refresh or info.bitmap is not bitmap + info.bitmap = bitmap + info.dis_bitmap = page_info.dis_bitmap + if should_refresh: + ctrl.Refresh() + ctrl.Update() + + return True + + + def GetPageBitmap(self, page_idx): + """ + Returns the tab bitmap for the page. + + :param `page_idx`: the page index. + """ + + if page_idx >= self._tabs.GetPageCount(): + return wx.NullBitmap + + # update our own tab catalog + page_info = self._tabs.GetPage(page_idx) + return page_info.bitmap + + + def SetImageList(self, imageList): + """ + Sets the image list for the L{AuiNotebook} control. + + :param `imageList`: an instance of `wx.ImageList`. + """ + + self._imageList = imageList + + + def AssignImageList(self, imageList): + """ + Sets the image list for the L{AuiNotebook} control. + + :param `imageList`: an instance of `wx.ImageList`. + """ + + self.SetImageList(imageList) + + + def GetImageList(self): + """ Returns the associated image list (if any). """ + + return self._imageList + + + def SetPageImage(self, page, image): + """ + Sets the image index for the given page. + + :param `page`: the page index; + :param `image`: an index into the image list which was set with L{SetImageList}. + """ + + if page >= self._tabs.GetPageCount(): + return False + + if not isinstance(image, types.IntType): + raise Exception("The image parameter must be an integer, you passed " \ + "%s"%repr(image)) + + if not self._imageList: + raise Exception("To use SetPageImage you need to associate an image list " \ + "Using SetImageList or AssignImageList") + + if image >= self._imageList.GetImageCount(): + raise Exception("Invalid image index (%d), the image list contains only" \ + " (%d) bitmaps"%(image, self._imageList.GetImageCount())) + + if image == -1: + self.SetPageBitmap(page, wx.NullBitmap) + return + + bitmap = self._imageList.GetBitmap(image) + self.SetPageBitmap(page, bitmap) + + + def GetPageImage(self, page): + """ + Returns the image index for the given page. + + :param `page`: the given page for which to retrieve the image index. + """ + + if page >= self._tabs.GetPageCount(): + return False + + bitmap = self.GetPageBitmap(page) + for indx in xrange(self._imageList.GetImageCount()): + imgListBmp = self._imageList.GetBitmap(indx) + if imgListBmp == bitmap: + return indx + + return wx.NOT_FOUND + + + def SetPageTextColour(self, page_idx, colour): + """ + Sets the tab text colour for the page. + + :param `page_idx`: the page index; + :param `colour`: an instance of `wx.Colour`. + """ + + if page_idx >= self._tabs.GetPageCount(): + return False + + # update our own tab catalog + page_info = self._tabs.GetPage(page_idx) + should_refresh = page_info.text_colour != colour + page_info.text_colour = colour + + # update what's on screen + ctrl, ctrl_idx = self.FindTab(page_info.window) + if not ctrl: + return False + + info = ctrl.GetPage(ctrl_idx) + should_refresh = should_refresh or info.text_colour != colour + info.text_colour = page_info.text_colour + + if should_refresh: + ctrl.Refresh() + ctrl.Update() + + return True + + + def GetPageTextColour(self, page_idx): + """ + Returns the tab text colour for the page. + + :param `page_idx`: the page index. + """ + + if page_idx >= self._tabs.GetPageCount(): + return wx.NullColour + + # update our own tab catalog + page_info = self._tabs.GetPage(page_idx) + return page_info.text_colour + + + def AddControlToPage(self, page_idx, control): + """ + Adds a control inside a tab (not in the tab area). + + :param `page_idx`: the page index; + :param `control`: an instance of `wx.Window`. + """ + + if page_idx >= self._tabs.GetPageCount(): + return False + + # update our own tab catalog + page_info = self._tabs.GetPage(page_idx) + page_info.control = control + + # tab height might have changed + self.UpdateTabCtrlHeight(force=True) + + # update what's on screen + ctrl, ctrl_idx = self.FindTab(page_info.window) + if not ctrl: + return False + + control.Reparent(ctrl) + + info = ctrl.GetPage(ctrl_idx) + info.control = control + ctrl.Refresh() + ctrl.Update() + + return True + + + def RemoveControlFromPage(self, page_idx): + """ + Removes a control from a tab (not from the tab area). + + :param `page_idx`: the page index. + """ + + if page_idx >= self._tabs.GetPageCount(): + return False + + page_info = self._tabs.GetPage(page_idx) + if page_info.control is None: + return False + + page_info.control.Destroy() + page_info.control = None + + # tab height might have changed + self.UpdateTabCtrlHeight(force=True) + + # update what's on screen + ctrl, ctrl_idx = self.FindTab(page_info.window) + if not ctrl: + return False + + info = ctrl.GetPage(ctrl_idx) + info.control = None + ctrl.Refresh() + ctrl.Update() + + return True + + + def SetCloseButton(self, page_idx, hasCloseButton): + """ + Sets whether a tab should display a close button or not. + + :param `page_idx`: the page index; + :param `hasCloseButton`: ``True`` if the page displays a close button. + + :note: This can only be called if ``AUI_NB_CLOSE_ON_ALL_TABS`` is specified. + """ + + if page_idx >= self._tabs.GetPageCount(): + return False + + if self._agwFlags & AUI_NB_CLOSE_ON_ALL_TABS == 0: + raise Exception("SetCloseButton can only be used with AUI_NB_CLOSE_ON_ALL_TABS style.") + + # update our own tab catalog + page_info = self._tabs.GetPage(page_idx) + page_info.hasCloseButton = hasCloseButton + + # update what's on screen + ctrl, ctrl_idx = self.FindTab(page_info.window) + if not ctrl: + return False + + info = ctrl.GetPage(ctrl_idx) + info.hasCloseButton = page_info.hasCloseButton + ctrl.Refresh() + ctrl.Update() + + return True + + + def HasCloseButton(self, page_idx): + """ + Returns whether a tab displays a close button or not. + + :param `page_idx`: the page index. + + :note: This can only be called if ``AUI_NB_CLOSE_ON_ALL_TABS`` is specified. + """ + + if page_idx >= self._tabs.GetPageCount(): + return False + + page_info = self._tabs.GetPage(page_idx) + return page_info.hasCloseButton + + + def GetSelection(self): + """ Returns the index of the currently active page, or -1 if none was selected. """ + + return self._curpage + + + def GetCurrentPage(self): + """ Returns the currently active page (not the index), or ``None`` if none was selected. """ + + if self._curpage >= 0 and self._curpage < self._tabs.GetPageCount(): + return self.GetPage(self._curpage) + + return None + + + def EnsureVisible(self, indx): + """ + Ensures the input page index `indx` is visible. + + :param `indx`: the page index. + """ + + self._tabs.MakeTabVisible(indx, self) + + + def SetSelection(self, new_page, force=False): + """ + Sets the page selection. Calling this method will generate a page change event. + + :param `new_page`: the index of the new selection; + :param `force`: whether to force the selection or not. + """ + wnd = self._tabs.GetWindowFromIdx(new_page) + + #Update page access time + self._tabs.GetPages()[new_page].access_time = datetime.datetime.now() + + if not wnd or not self.GetEnabled(new_page): + return self._curpage + + # don't change the page unless necessary + # however, clicking again on a tab should give it the focus. + if new_page == self._curpage and not force: + + ctrl, ctrl_idx = self.FindTab(wnd) + if wx.Window.FindFocus() != ctrl: + ctrl.SetFocus() + + return self._curpage + + evt = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_PAGE_CHANGING, self.GetId()) + evt.SetSelection(new_page) + evt.SetOldSelection(self._curpage) + evt.SetEventObject(self) + + if not self.GetEventHandler().ProcessEvent(evt) or evt.IsAllowed(): + + old_curpage = self._curpage + self._curpage = new_page + + # program allows the page change + evt.SetEventType(wxEVT_COMMAND_AUINOTEBOOK_PAGE_CHANGED) + self.GetEventHandler().ProcessEvent(evt) + + if not evt.IsAllowed(): # event is no longer allowed after handler + return self._curpage + + ctrl, ctrl_idx = self.FindTab(wnd) + + if ctrl: + self._tabs.SetActivePage(wnd) + ctrl.SetActivePage(ctrl_idx) + self.DoSizing() + ctrl.DoShowHide() + ctrl.MakeTabVisible(ctrl_idx, ctrl) + + # set fonts + all_panes = self._mgr.GetAllPanes() + for pane in all_panes: + if pane.name == "dummy": + continue + + tabctrl = pane.window._tabs + if tabctrl != ctrl: + tabctrl.SetSelectedFont(self._normal_font) + else: + tabctrl.SetSelectedFont(self._selected_font) + + tabctrl.Refresh() + tabctrl.Update() + + # Set the focus to the page if we're not currently focused on the tab. + # This is Firefox-like behaviour. + if wnd.IsShownOnScreen() and wx.Window.FindFocus() != ctrl: + wnd.SetFocus() + + return old_curpage + + return self._curpage + + + def SetSelectionToWindow(self, win): + """ + Sets the selection based on the input window `win`. + + :param `win`: a `wx.Window` derived window. + """ + + idx = self._tabs.GetIdxFromWindow(win) + + if idx == wx.NOT_FOUND: + raise Exception("invalid notebook page") + + if not self.GetEnabled(idx): + return + + # since a tab was clicked, let the parent know that we received + # the focus, even if we will assign that focus immediately + # to the child tab in the SetSelection call below + # (the child focus event will also let AuiManager, if any, + # know that the notebook control has been activated) + + parent = self.GetParent() + if parent: + eventFocus = wx.ChildFocusEvent(self) + parent.GetEventHandler().ProcessEvent(eventFocus) + + self.SetSelection(idx) + + + def SetSelectionToPage(self, page): + """ + Sets the selection based on the input page. + + :param `page`: an instance of L{AuiNotebookPage}. + """ + + self.SetSelectionToWindow(page.window) + + + def GetPageCount(self): + """ Returns the number of pages in the notebook. """ + + return self._tabs.GetPageCount() + + + def GetPage(self, page_idx): + """ + Returns the page specified by the given index. + + :param `page_idx`: the page index. + """ + + if page_idx >= self._tabs.GetPageCount(): + raise Exception("invalid notebook page") + + return self._tabs.GetWindowFromIdx(page_idx) + + + def GetPageInfo(self, page_idx): + """ + Returns the L{AuiNotebookPage} info structure specified by the given index. + + :param `page_idx`: the page index. + """ + + if page_idx >= self._tabs.GetPageCount(): + raise Exception("invalid notebook page") + + return self._tabs.GetPage(page_idx) + + + def GetEnabled(self, page_idx): + """ + Returns whether the page specified by the index `page_idx` is enabled. + + :param `page_idx`: the page index. + """ + + return self._tabs.GetEnabled(page_idx) + + + def EnableTab(self, page_idx, enable=True): + """ + Enables/disables a page in the notebook. + + :param `page_idx`: the page index; + :param `enable`: ``True`` to enable the page, ``False`` to disable it. + """ + + self._tabs.EnableTab(page_idx, enable) + self.Refresh() + + + def DoSizing(self): + """ Performs all sizing operations in each tab control. """ + + all_panes = self._mgr.GetAllPanes() + for pane in all_panes: + if pane.name == "dummy": + continue + + tabframe = pane.window + tabframe.DoSizing() + + + def GetAuiManager(self): + """ Returns the associated L{AuiManager}. """ + + return self._mgr + + + def GetActiveTabCtrl(self): + """ + Returns the active tab control. It is called to determine which control + gets new windows being added. + """ + + if self._curpage >= 0 and self._curpage < self._tabs.GetPageCount(): + + # find the tab ctrl with the current page + ctrl, idx = self.FindTab(self._tabs.GetPage(self._curpage).window) + if ctrl: + return ctrl + + # no current page, just find the first tab ctrl + all_panes = self._mgr.GetAllPanes() + for pane in all_panes: + if pane.name == "dummy": + continue + + tabframe = pane.window + return tabframe._tabs + + # If there is no tabframe at all, create one + tabframe = TabFrame(self) + tabframe.SetTabCtrlHeight(self._tab_ctrl_height) + self._tab_id_counter += 1 + tabframe._tabs = AuiTabCtrl(self, self._tab_id_counter) + + tabframe._tabs.SetAGWFlags(self._agwFlags) + tabframe._tabs.SetArtProvider(self._tabs.GetArtProvider().Clone()) + self._mgr.AddPane(tabframe, framemanager.AuiPaneInfo().Center().CaptionVisible(False). + PaneBorder((self._agwFlags & AUI_NB_SUB_NOTEBOOK) == 0)) + + self._mgr.Update() + + return tabframe._tabs + + + def FindTab(self, page): + """ + Finds the tab control that currently contains the window as well + as the index of the window in the tab control. It returns ``True`` if the + window was found, otherwise ``False``. + + :param `page`: an instance of L{AuiNotebookPage}. + """ + + all_panes = self._mgr.GetAllPanes() + for pane in all_panes: + if pane.name == "dummy": + continue + + tabframe = pane.window + + page_idx = tabframe._tabs.GetIdxFromWindow(page) + + if page_idx != -1: + + ctrl = tabframe._tabs + idx = page_idx + return ctrl, idx + + return None, wx.NOT_FOUND + + + def Split(self, page, direction): + """ + Performs a split operation programmatically. + + :param `page`: indicates the page that will be split off. This page will also become + the active page after the split. + :param `direction`: specifies where the pane should go, it should be one of the + following: ``wx.TOP``, ``wx.BOTTOM``, ``wx.LEFT``, or ``wx.RIGHT``. + """ + + cli_size = self.GetClientSize() + + # get the page's window pointer + wnd = self.GetPage(page) + if not wnd: + return + + # notebooks with 1 or less pages can't be split + if self.GetPageCount() < 2: + return + + # find out which tab control the page currently belongs to + + src_tabs, src_idx = self.FindTab(wnd) + if not src_tabs: + return + + # choose a split size + if self.GetPageCount() > 2: + split_size = self.CalculateNewSplitSize() + else: + # because there are two panes, always split them + # equally + split_size = self.GetClientSize() + split_size.x /= 2 + split_size.y /= 2 + + # create a new tab frame + new_tabs = TabFrame(self) + new_tabs._rect = wx.RectPS(wx.Point(0, 0), split_size) + new_tabs.SetTabCtrlHeight(self._tab_ctrl_height) + self._tab_id_counter += 1 + new_tabs._tabs = AuiTabCtrl(self, self._tab_id_counter) + + new_tabs._tabs.SetArtProvider(self._tabs.GetArtProvider().Clone()) + new_tabs._tabs.SetAGWFlags(self._agwFlags) + dest_tabs = new_tabs._tabs + + page_info = src_tabs.GetPage(src_idx) + if page_info.control: + self.ReparentControl(page_info.control, dest_tabs) + + # create a pane info structure with the information + # about where the pane should be added + pane_info = framemanager.AuiPaneInfo().Bottom().CaptionVisible(False) + + if direction == wx.LEFT: + + pane_info.Left() + mouse_pt = wx.Point(0, cli_size.y/2) + + elif direction == wx.RIGHT: + + pane_info.Right() + mouse_pt = wx.Point(cli_size.x, cli_size.y/2) + + elif direction == wx.TOP: + + pane_info.Top() + mouse_pt = wx.Point(cli_size.x/2, 0) + + elif direction == wx.BOTTOM: + + pane_info.Bottom() + mouse_pt = wx.Point(cli_size.x/2, cli_size.y) + + self._mgr.AddPane(new_tabs, pane_info, mouse_pt) + self._mgr.Update() + + # remove the page from the source tabs + page_info.active = False + + src_tabs.RemovePage(page_info.window) + + if src_tabs.GetPageCount() > 0: + src_tabs.SetActivePage(0) + src_tabs.DoShowHide() + src_tabs.Refresh() + + # add the page to the destination tabs + dest_tabs.InsertPage(page_info.window, page_info, 0) + + if src_tabs.GetPageCount() == 0: + self.RemoveEmptyTabFrames() + + self.DoSizing() + dest_tabs.DoShowHide() + dest_tabs.Refresh() + + # force the set selection function reset the selection + self._curpage = -1 + + # set the active page to the one we just split off + self.SetSelectionToPage(page_info) + + self.UpdateHintWindowSize() + + + def UnSplit(self): + """ Restores original view after a tab split. """ + + self.Freeze() + + # remember the tab now selected + nowSelected = self.GetSelection() + # select first tab as destination + self.SetSelection(0) + # iterate all other tabs + for idx in xrange(1, self.GetPageCount()): + # get win reference + win = self.GetPage(idx) + # get tab title + title = self.GetPageText(idx) + # get page bitmap + bmp = self.GetPageBitmap(idx) + # remove from notebook + self.RemovePage(idx) + # re-add in the same position so it will tab + self.InsertPage(idx, win, title, False, bmp) + # restore orignial selected tab + self.SetSelection(nowSelected) + + self.Thaw() + + + def ReparentControl(self, control, dest_tabs): + """ + Reparents a control added inside a tab. + + :param `control`: an instance of `wx.Window`; + :param `dest_tabs`: the destination L{AuiTabCtrl}. + """ + + control.Hide() + control.Reparent(dest_tabs) + + + def UnsplitDClick(self, part, sash_size, pos): + """ + Unsplit the L{AuiNotebook} on sash double-click. + + :param `part`: an UI part representing the sash; + :param `sash_size`: the sash size; + :param `pos`: the double-click mouse position. + + :warning: Due to a bug on MSW, for disabled pages `wx.FindWindowAtPoint` + returns the wrong window. See http://trac.wxwidgets.org/ticket/2942 + """ + + if not self._sash_dclick_unsplit: + # Unsplit not allowed + return + + pos1 = wx.Point(*pos) + pos2 = wx.Point(*pos) + if part.orientation == wx.HORIZONTAL: + pos1.y -= 2*sash_size + pos2.y += 2*sash_size + self.GetTabCtrlHeight() + elif part.orientation == wx.VERTICAL: + pos1.x -= 2*sash_size + pos2.x += 2*sash_size + else: + raise Exception("Invalid UI part orientation") + + pos1, pos2 = self.ClientToScreen(pos1), self.ClientToScreen(pos2) + win1, win2 = wx.FindWindowAtPoint(pos1), wx.FindWindowAtPoint(pos2) + + if isinstance(win1, wx.ScrollBar): + # Hopefully it will work + pos1 = wx.Point(*pos) + shift = wx.SystemSettings.GetMetric(wx.SYS_VSCROLL_X) + 2*(sash_size+1) + if part.orientation == wx.HORIZONTAL: + pos1.y -= shift + else: + pos1.x -= shift + + pos1 = self.ClientToScreen(pos1) + win1 = wx.FindWindowAtPoint(pos1) + + if isinstance(win2, wx.ScrollBar): + pos2 = wx.Point(*pos) + shift = wx.SystemSettings.GetMetric(wx.SYS_VSCROLL_X) + 2*(sash_size+1) + if part.orientation == wx.HORIZONTAL: + pos2.y += shift + else: + pos2.x += shift + + pos2 = self.ClientToScreen(pos2) + win2 = wx.FindWindowAtPoint(pos2) + + if not win1 or not win2: + # How did we get here? + return + + if isinstance(win1, AuiNotebook) or isinstance(win2, AuiNotebook): + # This is a bug on MSW, for disabled pages wx.FindWindowAtPoint + # returns the wrong window. + # See http://trac.wxwidgets.org/ticket/2942 + return + + tab_frame1, tab_frame2 = self.GetTabFrameFromWindow(win1), self.GetTabFrameFromWindow(win2) + + if not tab_frame1 or not tab_frame2: + return + + tab_ctrl_1, tab_ctrl_2 = tab_frame1._tabs, tab_frame2._tabs + + if tab_ctrl_1.GetPageCount() > tab_ctrl_2.GetPageCount(): + src_tabs = tab_ctrl_2 + dest_tabs = tab_ctrl_1 + else: + src_tabs = tab_ctrl_1 + dest_tabs = tab_ctrl_2 + + selection = -1 + page_count = dest_tabs.GetPageCount() + + for page in xrange(src_tabs.GetPageCount()-1, -1, -1): + # remove the page from the source tabs + page_info = src_tabs.GetPage(page) + if page_info.active: + selection = page_count + page + src_tabs.RemovePage(page_info.window) + + # add the page to the destination tabs + dest_tabs.AddPage(page_info.window, page_info) + if page_info.control: + self.ReparentControl(page_info.control, dest_tabs) + + self.RemoveEmptyTabFrames() + + dest_tabs.DoShowHide() + self.DoSizing() + dest_tabs.Refresh() + self._mgr.Update() + if selection > 0: + wx.CallAfter(dest_tabs.MakeTabVisible, selection, self) + + + def OnSize(self, event): + """ + Handles the ``wx.EVT_SIZE`` event for L{AuiNotebook}. + + :param `event`: a `wx.SizeEvent` event to be processed. + """ + + self.UpdateHintWindowSize() + event.Skip() + + + def OnTabClicked(self, event): + """ + Handles the ``EVT_AUINOTEBOOK_PAGE_CHANGING`` event for L{AuiNotebook}. + + :param `event`: a L{AuiNotebookEvent} event to be processed. + """ + + if self._textCtrl is not None: + self._textCtrl.StopEditing() + + ctrl = event.GetEventObject() + assert ctrl != None + + wnd = ctrl.GetWindowFromIdx(event.GetSelection()) + assert wnd != None + + self.SetSelectionToWindow(wnd) + + + def OnTabBgDClick(self, event): + """ + Handles the ``EVT_AUINOTEBOOK_BG_DCLICK`` event for L{AuiNotebook}. + + :param `event`: a L{AuiNotebookEvent} event to be processed. + """ + + if self._textCtrl is not None: + self._textCtrl.StopEditing() + + # notify owner that the tabbar background has been double-clicked + e = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_BG_DCLICK, self.GetId()) + e.SetEventObject(self) + self.GetEventHandler().ProcessEvent(e) + + + def OnTabDClick(self, event): + """ + Handles the ``EVT_AUINOTEBOOK_TAB_DCLICK`` event for L{AuiNotebook}. + + :param `event`: a L{AuiNotebookEvent} event to be processed. + """ + + # notify owner that the tabbar background has been double-clicked + e = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_TAB_DCLICK, self.GetId()) + e.SetEventObject(self) + self.GetEventHandler().ProcessEvent(e) + + tabs = event.GetEventObject() + if not tabs.GetEnabled(event.GetSelection()): + return + + if not self.IsRenamable(event.GetSelection()): + return + + self.EditTab(event.GetSelection()) + + + def OnTabBeginDrag(self, event): + """ + Handles the ``EVT_AUINOTEBOOK_BEGIN_DRAG`` event for L{AuiNotebook}. + + :param `event`: a L{AuiNotebookEvent} event to be processed. + """ + + tabs = event.GetEventObject() + if not tabs.GetEnabled(event.GetSelection()): + return + + self._last_drag_x = 0 + + + def OnTabDragMotion(self, event): + """ + Handles the ``EVT_AUINOTEBOOK_DRAG_MOTION`` event for L{AuiNotebook}. + + :param `event`: a L{AuiNotebookEvent} event to be processed. + """ + + tabs = event.GetEventObject() + if not tabs.GetEnabled(event.GetSelection()): + return + + if self._textCtrl is not None: + self._textCtrl.StopEditing() + + screen_pt = wx.GetMousePosition() + client_pt = self.ScreenToClient(screen_pt) + zero = wx.Point(0, 0) + + src_tabs = event.GetEventObject() + dest_tabs = self.GetTabCtrlFromPoint(client_pt) + + if dest_tabs == src_tabs: + + # always hide the hint for inner-tabctrl drag + self._mgr.HideHint() + + # if tab moving is not allowed, leave + if not self._agwFlags & AUI_NB_TAB_MOVE: + return + + pt = dest_tabs.ScreenToClient(screen_pt) + + # this is an inner-tab drag/reposition + dest_location_tab = dest_tabs.TabHitTest(pt.x, pt.y) + + if dest_location_tab: + + src_idx = event.GetSelection() + dest_idx = dest_tabs.GetIdxFromWindow(dest_location_tab) + + # prevent jumpy drag + if (src_idx == dest_idx) or dest_idx == -1 or \ + (src_idx > dest_idx and self._last_drag_x <= pt.x) or \ + (src_idx < dest_idx and self._last_drag_x >= pt.x): + + self._last_drag_x = pt.x + return + + src_tab = dest_tabs.GetWindowFromIdx(src_idx) + dest_tabs.MovePage(src_tab, dest_idx) + self._tabs.MovePage(self._tabs.GetPage(src_idx).window, dest_idx) + dest_tabs.SetActivePage(dest_idx) + dest_tabs.DoShowHide() + dest_tabs.Refresh() + self._last_drag_x = pt.x + + return + + # if external drag is allowed, check if the tab is being dragged + # over a different AuiNotebook control + if self._agwFlags & AUI_NB_TAB_EXTERNAL_MOVE: + + tab_ctrl = wx.FindWindowAtPoint(screen_pt) + + # if we aren't over any window, stop here + if not tab_ctrl: + if self._agwFlags & AUI_NB_TAB_FLOAT: + if self.IsMouseWellOutsideWindow(): + hintRect = wx.RectPS(screen_pt, (400, 300)) + # Use CallAfter so we overwrite the hint that might be + # shown by our superclass: + wx.CallAfter(self._mgr.ShowHint, hintRect) + return + + # make sure we are not over the hint window + if not isinstance(tab_ctrl, wx.Frame): + while tab_ctrl: + if isinstance(tab_ctrl, AuiTabCtrl): + break + + tab_ctrl = tab_ctrl.GetParent() + + if tab_ctrl: + nb = tab_ctrl.GetParent() + + if nb != self: + + hint_rect = tab_ctrl.GetClientRect() + hint_rect.x, hint_rect.y = tab_ctrl.ClientToScreenXY(hint_rect.x, hint_rect.y) + self._mgr.ShowHint(hint_rect) + return + + else: + + if not dest_tabs: + # we are either over a hint window, or not over a tab + # window, and there is no where to drag to, so exit + return + + if self._agwFlags & AUI_NB_TAB_FLOAT: + if self.IsMouseWellOutsideWindow(): + hintRect = wx.RectPS(screen_pt, (400, 300)) + # Use CallAfter so we overwrite the hint that might be + # shown by our superclass: + wx.CallAfter(self._mgr.ShowHint, hintRect) + return + + # if there are less than two panes, split can't happen, so leave + if self._tabs.GetPageCount() < 2: + return + + # if tab moving is not allowed, leave + if not self._agwFlags & AUI_NB_TAB_SPLIT: + return + + if dest_tabs: + + hint_rect = dest_tabs.GetRect() + hint_rect.x, hint_rect.y = self.ClientToScreenXY(hint_rect.x, hint_rect.y) + self._mgr.ShowHint(hint_rect) + + else: + rect = self._mgr.CalculateHintRect(self._dummy_wnd, client_pt, zero) + if rect.IsEmpty(): + self._mgr.HideHint() + return + + hit_wnd = wx.FindWindowAtPoint(screen_pt) + if hit_wnd and not isinstance(hit_wnd, AuiNotebook): + tab_frame = self.GetTabFrameFromWindow(hit_wnd) + if tab_frame: + hint_rect = wx.Rect(*tab_frame._rect) + hint_rect.x, hint_rect.y = self.ClientToScreenXY(hint_rect.x, hint_rect.y) + rect.Intersect(hint_rect) + self._mgr.ShowHint(rect) + else: + self._mgr.DrawHintRect(self._dummy_wnd, client_pt, zero) + else: + self._mgr.DrawHintRect(self._dummy_wnd, client_pt, zero) + + + def OnTabEndDrag(self, event): + """ + Handles the ``EVT_AUINOTEBOOK_END_DRAG`` event for L{AuiNotebook}. + + :param `event`: a L{AuiNotebookEvent} event to be processed. + """ + + tabs = event.GetEventObject() + if not tabs.GetEnabled(event.GetSelection()): + return + + self._mgr.HideHint() + + src_tabs = event.GetEventObject() + if not src_tabs: + raise Exception("no source object?") + + # get the mouse position, which will be used to determine the drop point + mouse_screen_pt = wx.GetMousePosition() + mouse_client_pt = self.ScreenToClient(mouse_screen_pt) + + # check for an external move + if self._agwFlags & AUI_NB_TAB_EXTERNAL_MOVE: + tab_ctrl = wx.FindWindowAtPoint(mouse_screen_pt) + + while tab_ctrl: + + if isinstance(tab_ctrl, AuiTabCtrl): + break + + tab_ctrl = tab_ctrl.GetParent() + + if tab_ctrl: + + nb = tab_ctrl.GetParent() + + if nb != self: + + # find out from the destination control + # if it's ok to drop this tab here + e = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_ALLOW_DND, self.GetId()) + e.SetSelection(event.GetSelection()) + e.SetOldSelection(event.GetSelection()) + e.SetEventObject(self) + e.SetDragSource(self) + e.Veto() # dropping must be explicitly approved by control owner + + nb.GetEventHandler().ProcessEvent(e) + + if not e.IsAllowed(): + + # no answer or negative answer + self._mgr.HideHint() + return + + # drop was allowed + src_idx = event.GetSelection() + src_page = src_tabs.GetWindowFromIdx(src_idx) + + # Check that it's not an impossible parent relationship + p = nb + while p and not p.IsTopLevel(): + if p == src_page: + return + + p = p.GetParent() + + # get main index of the page + main_idx = self._tabs.GetIdxFromWindow(src_page) + if main_idx == wx.NOT_FOUND: + raise Exception("no source page?") + + # make a copy of the page info + page_info = self._tabs.GetPage(main_idx) + + # remove the page from the source notebook + self.RemovePage(main_idx) + + # reparent the page + src_page.Reparent(nb) + + # Reparent the control in a tab (if any) + if page_info.control: + self.ReparentControl(page_info.control, tab_ctrl) + + # find out the insert idx + dest_tabs = tab_ctrl + pt = dest_tabs.ScreenToClient(mouse_screen_pt) + + target = dest_tabs.TabHitTest(pt.x, pt.y) + insert_idx = -1 + if target: + insert_idx = dest_tabs.GetIdxFromWindow(target) + + # add the page to the new notebook + if insert_idx == -1: + insert_idx = dest_tabs.GetPageCount() + + dest_tabs.InsertPage(page_info.window, page_info, insert_idx) + nb._tabs.AddPage(page_info.window, page_info) + + nb.DoSizing() + dest_tabs.DoShowHide() + dest_tabs.Refresh() + + # set the selection in the destination tab control + nb.SetSelectionToPage(page_info) + + # notify owner that the tab has been dragged + e2 = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_DRAG_DONE, self.GetId()) + e2.SetSelection(event.GetSelection()) + e2.SetOldSelection(event.GetSelection()) + e2.SetEventObject(self) + self.GetEventHandler().ProcessEvent(e2) + + # notify the target notebook that the tab has been dragged + e3 = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_DRAG_DONE, nb.GetId()) + e3.SetSelection(insert_idx) + e3.SetOldSelection(insert_idx) + e3.SetEventObject(nb) + nb.GetEventHandler().ProcessEvent(e3) + + return + + if self._agwFlags & AUI_NB_TAB_FLOAT: + self._mgr.HideHint() + if self.IsMouseWellOutsideWindow(): + # Use CallAfter so we our superclass can deal with the event first + wx.CallAfter(self.FloatPage, self.GetSelection()) + event.Skip() + return + + # only perform a tab split if it's allowed + dest_tabs = None + + if self._agwFlags & AUI_NB_TAB_SPLIT and self._tabs.GetPageCount() >= 2: + + # If the pointer is in an existing tab frame, do a tab insert + hit_wnd = wx.FindWindowAtPoint(mouse_screen_pt) + tab_frame = self.GetTabFrameFromTabCtrl(hit_wnd) + insert_idx = -1 + + if tab_frame: + + dest_tabs = tab_frame._tabs + + if dest_tabs == src_tabs: + return + + pt = dest_tabs.ScreenToClient(mouse_screen_pt) + target = dest_tabs.TabHitTest(pt.x, pt.y) + + if target: + insert_idx = dest_tabs.GetIdxFromWindow(target) + + else: + + zero = wx.Point(0, 0) + rect = self._mgr.CalculateHintRect(self._dummy_wnd, mouse_client_pt, zero) + + if rect.IsEmpty(): + # there is no suitable drop location here, exit out + return + + # If there is no tabframe at all, create one + new_tabs = TabFrame(self) + new_tabs._rect = wx.RectPS(wx.Point(0, 0), self.CalculateNewSplitSize()) + new_tabs.SetTabCtrlHeight(self._tab_ctrl_height) + self._tab_id_counter += 1 + new_tabs._tabs = AuiTabCtrl(self, self._tab_id_counter) + new_tabs._tabs.SetArtProvider(self._tabs.GetArtProvider().Clone()) + new_tabs._tabs.SetAGWFlags(self._agwFlags) + + self._mgr.AddPane(new_tabs, framemanager.AuiPaneInfo().Bottom().CaptionVisible(False), mouse_client_pt) + self._mgr.Update() + dest_tabs = new_tabs._tabs + + # remove the page from the source tabs + page_info = src_tabs.GetPage(event.GetSelection()) + + if page_info.control: + self.ReparentControl(page_info.control, dest_tabs) + + page_info.active = False + src_tabs.RemovePage(page_info.window) + + if src_tabs.GetPageCount() > 0: + src_tabs.SetActivePage(0) + src_tabs.DoShowHide() + src_tabs.Refresh() + + # add the page to the destination tabs + if insert_idx == -1: + insert_idx = dest_tabs.GetPageCount() + + dest_tabs.InsertPage(page_info.window, page_info, insert_idx) + + if src_tabs.GetPageCount() == 0: + self.RemoveEmptyTabFrames() + + self.DoSizing() + dest_tabs.DoShowHide() + dest_tabs.Refresh() + + # force the set selection function reset the selection + self._curpage = -1 + + # set the active page to the one we just split off + self.SetSelectionToPage(page_info) + + self.UpdateHintWindowSize() + + # notify owner that the tab has been dragged + e = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_DRAG_DONE, self.GetId()) + e.SetSelection(event.GetSelection()) + e.SetOldSelection(event.GetSelection()) + e.SetEventObject(self) + self.GetEventHandler().ProcessEvent(e) + + + def OnTabCancelDrag(self, event): + """ + Handles the ``EVT_AUINOTEBOOK_CANCEL_DRAG`` event for L{AuiNotebook}. + + :param `event`: a L{AuiNotebookEvent} event to be processed. + """ + + tabs = event.GetEventObject() + if not tabs.GetEnabled(event.GetSelection()): + return + + self._mgr.HideHint() + + src_tabs = event.GetEventObject() + if not src_tabs: + raise Exception("no source object?") + + + def IsMouseWellOutsideWindow(self): + """ Returns whether the mouse is well outside the L{AuiNotebook} screen rectangle. """ + + screen_rect = self.GetScreenRect() + screen_rect.Inflate(50, 50) + + return not screen_rect.Contains(wx.GetMousePosition()) + + + def FloatPage(self, page_index): + """ + Float the page in `page_index` by reparenting it to a floating frame. + + :param `page_index`: the index of the page to be floated. + + :warning: When the notebook is more or less full screen, tabs cannot be dragged far + enough outside of the notebook to become floating pages. + """ + + root_manager = framemanager.GetManager(self) + page_title = self.GetPageText(page_index) + page_contents = self.GetPage(page_index) + page_bitmap = self.GetPageBitmap(page_index) + text_colour = self.GetPageTextColour(page_index) + info = self.GetPageInfo(page_index) + + if root_manager and root_manager != self._mgr: + root_manager = framemanager.GetManager(self) + + if hasattr(page_contents, "__floating_size__"): + floating_size = wx.Size(*page_contents.__floating_size__) + else: + floating_size = page_contents.GetBestSize() + if floating_size == wx.DefaultSize: + floating_size = wx.Size(300, 200) + + page_contents.__page_index__ = page_index + page_contents.__aui_notebook__ = self + page_contents.__text_colour__ = text_colour + page_contents.__control__ = info.control + + if info.control: + info.control.Reparent(page_contents) + info.control.Hide() + info.control = None + + self.RemovePage(page_index) + self.RemoveEmptyTabFrames() + + pane_info = framemanager.AuiPaneInfo().Float().FloatingPosition(wx.GetMousePosition()). \ + FloatingSize(floating_size).BestSize(floating_size).Name("__floating__%s"%page_title). \ + Caption(page_title).Icon(page_bitmap) + root_manager.AddPane(page_contents, pane_info) + root_manager.Bind(framemanager.EVT_AUI_PANE_CLOSE, self.OnCloseFloatingPage) + self.GetActiveTabCtrl().DoShowHide() + self.DoSizing() + root_manager.Update() + + else: + frame = wx.Frame(self, title=page_title, + style=wx.DEFAULT_FRAME_STYLE|wx.FRAME_TOOL_WINDOW| + wx.FRAME_FLOAT_ON_PARENT | wx.FRAME_NO_TASKBAR) + + if info.control: + info.control.Reparent(frame) + info.control.Hide() + + frame.bitmap = page_bitmap + frame.page_index = page_index + frame.text_colour = text_colour + frame.control = info.control + page_contents.Reparent(frame) + frame.Bind(wx.EVT_CLOSE, self.OnCloseFloatingPage) + frame.Move(wx.GetMousePosition()) + frame.Show() + self.RemovePage(page_index) + + self.RemoveEmptyTabFrames() + + wx.CallAfter(self.RemoveEmptyTabFrames) + + + def OnCloseFloatingPage(self, event): + """ + Handles the ``wx.EVT_CLOSE`` event for a floating page in L{AuiNotebook}. + + :param `event`: a `wx.CloseEvent` event to be processed. + """ + + root_manager = framemanager.GetManager(self) + if root_manager and root_manager != self._mgr: + pane = event.pane + if pane.name.startswith("__floating__"): + self.ReDockPage(pane) + return + + event.Skip() + else: + event.Skip() + frame = event.GetEventObject() + page_title = frame.GetTitle() + page_contents = list(frame.GetChildren())[-1] + page_contents.Reparent(self) + self.InsertPage(frame.page_index, page_contents, page_title, select=True, bitmap=frame.bitmap, control=frame.control) + + if frame.control: + src_tabs, idx = self.FindTab(page_contents) + frame.control.Reparent(src_tabs) + frame.control.Hide() + frame.control = None + + self.SetPageTextColour(frame.page_index, frame.text_colour) + + + def ReDockPage(self, pane): + """ + Re-docks a floating L{AuiNotebook} tab in the original position, when possible. + + :param `pane`: an instance of L{framemanager.AuiPaneInfo}. + """ + + root_manager = framemanager.GetManager(self) + + pane.window.__floating_size__ = wx.Size(*pane.floating_size) + page_index = pane.window.__page_index__ + text_colour = pane.window.__text_colour__ + control = pane.window.__control__ + + root_manager.DetachPane(pane.window) + self.InsertPage(page_index, pane.window, pane.caption, True, pane.icon, control=control) + + self.SetPageTextColour(page_index, text_colour) + self.GetActiveTabCtrl().DoShowHide() + self.DoSizing() + if control: + self.UpdateTabCtrlHeight(force=True) + + self._mgr.Update() + root_manager.Update() + + + def GetTabCtrlFromPoint(self, pt): + """ + Returns the tab control at the specified point. + + :param `pt`: a `wx.Point` object. + """ + + # if we've just removed the last tab from the source + # tab set, the remove the tab control completely + all_panes = self._mgr.GetAllPanes() + for pane in all_panes: + if pane.name == "dummy": + continue + + tabframe = pane.window + if tabframe._tab_rect.Contains(pt): + return tabframe._tabs + + return None + + + def GetTabFrameFromTabCtrl(self, tab_ctrl): + """ + Returns the tab frame associated with a tab control. + + :param `tab_ctrl`: an instance of L{AuiTabCtrl}. + """ + + # if we've just removed the last tab from the source + # tab set, the remove the tab control completely + all_panes = self._mgr.GetAllPanes() + for pane in all_panes: + if pane.name == "dummy": + continue + + tabframe = pane.window + if tabframe._tabs == tab_ctrl: + return tabframe + + return None + + + def GetTabFrameFromWindow(self, wnd): + """ + Returns the tab frame associated with a window. + + :param `wnd`: an instance of `wx.Window`. + """ + + all_panes = self._mgr.GetAllPanes() + for pane in all_panes: + if pane.name == "dummy": + continue + + tabframe = pane.window + for page in tabframe._tabs.GetPages(): + if wnd == page.window: + return tabframe + + return None + + + def RemoveEmptyTabFrames(self): + """ Removes all the empty tab frames. """ + + # if we've just removed the last tab from the source + # tab set, the remove the tab control completely + all_panes = self._mgr.GetAllPanes() + + for indx in xrange(len(all_panes)-1, -1, -1): + pane = all_panes[indx] + if pane.name == "dummy": + continue + + tab_frame = pane.window + if tab_frame._tabs.GetPageCount() == 0: + self._mgr.DetachPane(tab_frame) + tab_frame._tabs.Destroy() + tab_frame._tabs = None + del tab_frame + + # check to see if there is still a center pane + # if there isn't, make a frame the center pane + first_good = None + center_found = False + + all_panes = self._mgr.GetAllPanes() + for pane in all_panes: + if pane.name == "dummy": + continue + + if pane.dock_direction == AUI_DOCK_CENTRE: + center_found = True + if not first_good: + first_good = pane.window + + if not center_found and first_good: + self._mgr.GetPane(first_good).Centre() + + if not self.IsBeingDeleted(): + self._mgr.Update() + + + def OnChildFocusNotebook(self, event): + """ + Handles the ``wx.EVT_CHILD_FOCUS`` event for L{AuiNotebook}. + + :param `event`: a `wx.ChildFocusEvent` event to be processed. + """ + + # if we're dragging a tab, don't change the current selection. + # This code prevents a bug that used to happen when the hint window + # was hidden. In the bug, the focus would return to the notebook + # child, which would then enter this handler and call + # SetSelection, which is not desired turn tab dragging. + + event.Skip() + + all_panes = self._mgr.GetAllPanes() + for pane in all_panes: + if pane.name == "dummy": + continue + tabframe = pane.window + if tabframe._tabs.IsDragging(): + return + +## # change the tab selection to the child +## # which was focused +## idx = self._tabs.GetIdxFromWindow(event.GetWindow()) +## if idx != -1 and idx != self._curpage: +## self.SetSelection(idx) + + + def SetNavigatorIcon(self, bmp): + """ + Sets the icon used by the L{TabNavigatorWindow}. + + :param `bmp`: an instance of `wx.Bitmap`. + """ + + if isinstance(bmp, wx.Bitmap) and bmp.IsOk(): + # Make sure image is proper size + if bmp.GetSize() != (16, 16): + img = bmp.ConvertToImage() + img.Rescale(16, 16, wx.IMAGE_QUALITY_HIGH) + bmp = wx.BitmapFromImage(img) + self._naviIcon = bmp + else: + raise TypeError, "SetNavigatorIcon requires a valid bitmap" + + + def OnNavigationKeyNotebook(self, event): + """ + Handles the ``wx.EVT_NAVIGATION_KEY`` event for L{AuiNotebook}. + + :param `event`: a `wx.NavigationKeyEvent` event to be processed. + """ + + if event.IsWindowChange(): + if self._agwFlags & AUI_NB_SMART_TABS: + if not self._popupWin: + self._popupWin = TabNavigatorWindow(self, self._naviIcon) + self._popupWin.SetReturnCode(wx.ID_OK) + self._popupWin.ShowModal() + idx = self._popupWin.GetSelectedPage() + self._popupWin.Destroy() + self._popupWin = None + # Need to do CallAfter so that the selection and its + # associated events get processed outside the context of + # this key event. Not doing so causes odd issues with the + # window focus under certain use cases on Windows. + wx.CallAfter(self.SetSelection, idx, True) + else: + # a dialog is already opened + self._popupWin.OnNavigationKey(event) + return + else: + # change pages + # FIXME: the problem with this is that if we have a split notebook, + # we selection may go all over the place. + self.AdvanceSelection(event.GetDirection()) + + else: + # we get this event in 3 cases + # + # a) one of our pages might have generated it because the user TABbed + # out from it in which case we should propagate the event upwards and + # our parent will take care of setting the focus to prev/next sibling + # + # or + # + # b) the parent panel wants to give the focus to us so that we + # forward it to our selected page. We can't deal with this in + # OnSetFocus() because we don't know which direction the focus came + # from in this case and so can't choose between setting the focus to + # first or last panel child + # + # or + # + # c) we ourselves (see MSWTranslateMessage) generated the event + # + parent = self.GetParent() + + # the wxObject* casts are required to avoid MinGW GCC 2.95.3 ICE + isFromParent = event.GetEventObject() == parent + isFromSelf = event.GetEventObject() == self + + if isFromParent or isFromSelf: + + # no, it doesn't come from child, case (b) or (c): forward to a + # page but only if direction is backwards (TAB) or from ourselves, + if self.GetSelection() != wx.NOT_FOUND and (not event.GetDirection() or isFromSelf): + + # so that the page knows that the event comes from it's parent + # and is being propagated downwards + event.SetEventObject(self) + + page = self.GetPage(self.GetSelection()) + if not page.GetEventHandler().ProcessEvent(event): + page.SetFocus() + + #else: page manages focus inside it itself + + else: # otherwise set the focus to the notebook itself + + self.SetFocus() + + else: + + # send this event back for the 'wraparound' focus. + winFocus = event.GetCurrentFocus() + + if winFocus: + event.SetEventObject(self) + winFocus.GetEventHandler().ProcessEvent(event) + + + def OnTabButton(self, event): + """ + Handles the ``EVT_AUINOTEBOOK_BUTTON`` event for L{AuiNotebook}. + + :param `event`: a L{AuiNotebookEvent} event to be processed. + """ + + tabs = event.GetEventObject() + button_id = event.GetInt() + + if button_id == AUI_BUTTON_CLOSE: + + selection = event.GetSelection() + + if selection == -1: + + # if the close button is to the right, use the active + # page selection to determine which page to close + selection = tabs.GetActivePage() + + if selection == -1 or not tabs.GetEnabled(selection): + return + + if selection != -1: + + close_wnd = tabs.GetWindowFromIdx(selection) + + if close_wnd.GetName() == "__fake__page__": + # This is a notebook preview + previous_active, page_status = close_wnd.__previousStatus + for page, status in zip(tabs.GetPages(), page_status): + page.enabled = status + + main_idx = self._tabs.GetIdxFromWindow(close_wnd) + self.DeletePage(main_idx) + + if previous_active >= 0: + tabs.SetActivePage(previous_active) + page_count = tabs.GetPageCount() + selection = -1 + + for page in xrange(page_count): + # remove the page from the source tabs + page_info = tabs.GetPage(page) + if page_info.active: + selection = page + break + + tabs.DoShowHide() + self.DoSizing() + tabs.Refresh() + + if selection >= 0: + wx.CallAfter(tabs.MakeTabVisible, selection, self) + + # Don't fire the event + return + + # ask owner if it's ok to close the tab + e = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_PAGE_CLOSE, self.GetId()) + idx = self._tabs.GetIdxFromWindow(close_wnd) + e.SetSelection(idx) + e.SetOldSelection(event.GetSelection()) + e.SetEventObject(self) + self.GetEventHandler().ProcessEvent(e) + if not e.IsAllowed(): + return + + if repr(close_wnd.__class__).find("AuiMDIChildFrame") >= 0: + close_wnd.Close() + + else: + main_idx = self._tabs.GetIdxFromWindow(close_wnd) + self.DeletePage(main_idx) + + # notify owner that the tab has been closed + e2 = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_PAGE_CLOSED, self.GetId()) + e2.SetSelection(idx) + e2.SetEventObject(self) + self.GetEventHandler().ProcessEvent(e2) + + if self.GetPageCount() == 0: + mgr = self.GetAuiManager() + win = mgr.GetManagedWindow() + win.SendSizeEvent() + + + def OnTabMiddleDown(self, event): + """ + Handles the ``EVT_AUINOTEBOOK_TAB_MIDDLE_DOWN`` event for L{AuiNotebook}. + + :param `event`: a L{AuiNotebookEvent} event to be processed. + """ + + tabs = event.GetEventObject() + if not tabs.GetEnabled(event.GetSelection()): + return + + # patch event through to owner + wnd = tabs.GetWindowFromIdx(event.GetSelection()) + + e = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_TAB_MIDDLE_DOWN, self.GetId()) + e.SetSelection(self._tabs.GetIdxFromWindow(wnd)) + e.SetEventObject(self) + self.GetEventHandler().ProcessEvent(e) + + + def OnTabMiddleUp(self, event): + """ + Handles the ``EVT_AUINOTEBOOK_TAB_MIDDLE_UP`` event for L{AuiNotebook}. + + :param `event`: a L{AuiNotebookEvent} event to be processed. + """ + + tabs = event.GetEventObject() + if not tabs.GetEnabled(event.GetSelection()): + return + + # if the AUI_NB_MIDDLE_CLICK_CLOSE is specified, middle + # click should act like a tab close action. However, first + # give the owner an opportunity to handle the middle up event + # for custom action + + wnd = tabs.GetWindowFromIdx(event.GetSelection()) + + e = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_TAB_MIDDLE_UP, self.GetId()) + e.SetSelection(self._tabs.GetIdxFromWindow(wnd)) + e.SetEventObject(self) + if self.GetEventHandler().ProcessEvent(e): + return + if not e.IsAllowed(): + return + + # check if we are supposed to close on middle-up + if self._agwFlags & AUI_NB_MIDDLE_CLICK_CLOSE == 0: + return + + # simulate the user pressing the close button on the tab + event.SetInt(AUI_BUTTON_CLOSE) + self.OnTabButton(event) + + + def OnTabRightDown(self, event): + """ + Handles the ``EVT_AUINOTEBOOK_TAB_RIGHT_DOWN`` event for L{AuiNotebook}. + + :param `event`: a L{AuiNotebookEvent} event to be processed. + """ + + tabs = event.GetEventObject() + if not tabs.GetEnabled(event.GetSelection()): + return + + # patch event through to owner + wnd = tabs.GetWindowFromIdx(event.GetSelection()) + + e = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_TAB_RIGHT_DOWN, self.GetId()) + e.SetSelection(self._tabs.GetIdxFromWindow(wnd)) + e.SetEventObject(self) + self.GetEventHandler().ProcessEvent(e) + + + def OnTabRightUp(self, event): + """ + Handles the ``EVT_AUINOTEBOOK_TAB_RIGHT_UP`` event for L{AuiNotebook}. + + :param `event`: a L{AuiNotebookEvent} event to be processed. + """ + + tabs = event.GetEventObject() + if not tabs.GetEnabled(event.GetSelection()): + return + + # patch event through to owner + wnd = tabs.GetWindowFromIdx(event.GetSelection()) + + e = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_TAB_RIGHT_UP, self.GetId()) + e.SetSelection(self._tabs.GetIdxFromWindow(wnd)) + e.SetEventObject(self) + self.GetEventHandler().ProcessEvent(e) + + + def SetNormalFont(self, font): + """ + Sets the normal font for drawing tab labels. + + :param `font`: a `wx.Font` object. + """ + + self._normal_font = font + self.GetArtProvider().SetNormalFont(font) + + + def SetSelectedFont(self, font): + """ + Sets the selected tab font for drawing tab labels. + + :param `font`: a `wx.Font` object. + """ + + self._selected_font = font + self.GetArtProvider().SetSelectedFont(font) + + + def SetMeasuringFont(self, font): + """ + Sets the font for calculating text measurements. + + :param `font`: a `wx.Font` object. + """ + + self.GetArtProvider().SetMeasuringFont(font) + + + def SetFont(self, font): + """ + Sets the tab font. + + :param `font`: a `wx.Font` object. + + :note: Overridden from `wx.PyPanel`. + """ + + wx.PyPanel.SetFont(self, font) + + selectedFont = wx.Font(font.GetPointSize(), font.GetFamily(), + font.GetStyle(), wx.BOLD, font.GetUnderlined(), + font.GetFaceName(), font.GetEncoding()) + + self.SetNormalFont(font) + self.SetSelectedFont(selectedFont) + self.SetMeasuringFont(selectedFont) + + # Recalculate tab container size based on new font + self.UpdateTabCtrlHeight(force=False) + self.DoSizing() + + return True + + + def GetTabCtrlHeight(self): + """ Returns the tab control height. """ + + return self._tab_ctrl_height + + + def GetHeightForPageHeight(self, pageHeight): + """ + Gets the height of the notebook for a given page height. + + :param `pageHeight`: the given page height. + """ + + self.UpdateTabCtrlHeight() + + tabCtrlHeight = self.GetTabCtrlHeight() + decorHeight = 2 + return tabCtrlHeight + pageHeight + decorHeight + + + def AdvanceSelection(self, forward=True, wrap=True): + """ + Cycles through the tabs. + + :param `forward`: whether to advance forward or backward; + :param `wrap`: ``True`` to return to the first tab if we reach the last tab. + + :note: The call to this function generates the page changing events. + """ + + tabCtrl = self.GetActiveTabCtrl() + newPage = -1 + + focusWin = tabCtrl.FindFocus() + activePage = tabCtrl.GetActivePage() + lenPages = len(tabCtrl.GetPages()) + + if lenPages == 1: + return False + + if forward: + if lenPages > 1: + + if activePage == -1 or activePage == lenPages - 1: + if not wrap: + return False + + newPage = 0 + + elif activePage < lenPages - 1: + newPage = activePage + 1 + + else: + + if lenPages > 1: + if activePage == -1 or activePage == 0: + if not wrap: + return False + + newPage = lenPages - 1 + + elif activePage > 0: + newPage = activePage - 1 + + + if newPage != -1: + if not self.GetEnabled(newPage): + return False + + e = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_PAGE_CHANGING, tabCtrl.GetId()) + e.SetSelection(newPage) + e.SetOldSelection(activePage) + e.SetEventObject(tabCtrl) + self.GetEventHandler().ProcessEvent(e) + +## if focusWin: +## focusWin.SetFocus() + + return True + + + def ShowWindowMenu(self): + """ + Shows the window menu for the active tab control associated with this + notebook, and returns ``True`` if a selection was made. + """ + + tabCtrl = self.GetActiveTabCtrl() + idx = tabCtrl.GetArtProvider().ShowDropDown(tabCtrl, tabCtrl.GetPages(), tabCtrl.GetActivePage()) + + if not self.GetEnabled(idx): + return False + + if idx != -1: + e = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_PAGE_CHANGING, tabCtrl.GetId()) + e.SetSelection(idx) + e.SetOldSelection(tabCtrl.GetActivePage()) + e.SetEventObject(tabCtrl) + self.GetEventHandler().ProcessEvent(e) + + return True + + else: + + return False + + + def AddTabAreaButton(self, id, location, normal_bitmap=wx.NullBitmap, disabled_bitmap=wx.NullBitmap): + """ + Adds a button in the tab area. + + :param `id`: the button identifier. This can be one of the following: + + ============================== ================================= + Button Identifier Description + ============================== ================================= + ``AUI_BUTTON_CLOSE`` Shows a close button on the tab area + ``AUI_BUTTON_WINDOWLIST`` Shows a window list button on the tab area + ``AUI_BUTTON_LEFT`` Shows a left button on the tab area + ``AUI_BUTTON_RIGHT`` Shows a right button on the tab area + ============================== ================================= + + :param `location`: the button location. Can be ``wx.LEFT`` or ``wx.RIGHT``; + :param `normal_bitmap`: the bitmap for an enabled tab; + :param `disabled_bitmap`: the bitmap for a disabled tab. + """ + + active_tabctrl = self.GetActiveTabCtrl() + active_tabctrl.AddButton(id, location, normal_bitmap, disabled_bitmap) + + + def RemoveTabAreaButton(self, id): + """ + Removes a button from the tab area. + + :param `id`: the button identifier. See L{AddTabAreaButton} for a list of button identifiers. + + :see: L{AddTabAreaButton} + """ + + active_tabctrl = self.GetActiveTabCtrl() + active_tabctrl.RemoveButton(id) + + + def HasMultiplePages(self): + """ + This method should be overridden to return ``True`` if this window has multiple pages. All + standard class with multiple pages such as `wx.Notebook`, `wx.Listbook` and `wx.Treebook` + already override it to return ``True`` and user-defined classes with similar behaviour + should do it as well to allow the library to handle such windows appropriately. + + :note: Overridden from `wx.PyPanel`. + """ + + return True + + + def GetDefaultBorder(self): + """ Returns the default border style for L{AuiNotebook}. """ + + return wx.BORDER_NONE + + + def NotebookPreview(self, thumbnail_size=200): + """ + Generates a preview of all the pages in the notebook (MSW and GTK only). + + :param `thumbnail_size`: the maximum size of every page thumbnail. + + :note: this functionality is currently unavailable on wxMac. + """ + + if wx.Platform == "__WXMAC__": + return False + + tabCtrl = self.GetActiveTabCtrl() + activePage = tabCtrl.GetActivePage() + pages = tabCtrl.GetPages() + + pageStatus, pageText = [], [] + + for indx, page in enumerate(pages): + + pageStatus.append(page.enabled) + + if not page.enabled: + continue + + self.SetSelectionToPage(page) + pageText.append(page.caption) + + rect = page.window.GetScreenRect() + bmp = RescaleScreenShot(TakeScreenShot(rect), thumbnail_size) + + page.enabled = False + if indx == 0: + il = wx.ImageList(bmp.GetWidth(), bmp.GetHeight(), True) + + il.Add(bmp) + + # create the list control + listCtrl = wx.ListCtrl(self, style=wx.LC_ICON|wx.LC_AUTOARRANGE|wx.LC_HRULES|wx.LC_VRULES, + name="__fake__page__") + + # assign the image list to it + listCtrl.AssignImageList(il, wx.IMAGE_LIST_NORMAL) + listCtrl.__previousStatus = [activePage, pageStatus] + + # create some items for the list + for indx, text in enumerate(pageText): + listCtrl.InsertImageStringItem(10000, text, indx) + + self.AddPage(listCtrl, "AuiNotebook Preview", True, bitmap=auinotebook_preview.GetBitmap(), disabled_bitmap=wx.NullBitmap) + return True + + + def SetRenamable(self, page_idx, renamable): + """ + Sets whether a tab can be renamed via a left double-click or not. + + :param `page_idx`: the page index; + :param `renamable`: ``True`` if the page can be renamed. + """ + + if page_idx >= self._tabs.GetPageCount(): + return False + + # update our own tab catalog + page_info = self._tabs.GetPage(page_idx) + page_info.renamable = renamable + + # update what's on screen + ctrl, ctrl_idx = self.FindTab(page_info.window) + if not ctrl: + return False + + info = ctrl.GetPage(ctrl_idx) + info.renamable = page_info.renamable + + return True + + + def IsRenamable(self, page_idx): + """ + Returns whether a tab can be renamed or not. + + :param `page_idx`: the page index. + + :returns: ``True`` is a page can be renamed, ``False`` otherwise. + """ + + if page_idx >= self._tabs.GetPageCount(): + return False + + page_info = self._tabs.GetPage(page_idx) + return page_info.renamable + + + def OnRenameCancelled(self, page_index): + """ + Called by L{TabTextCtrl}, to cancel the changes and to send the + `EVT_AUINOTEBOOK_END_LABEL_EDIT` event. + + :param `page_index`: the page index in the notebook. + """ + + # let owner know that the edit was cancelled + evt = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_END_LABEL_EDIT, self.GetId()) + + evt.SetSelection(page_index) + evt.SetEventObject(self) + evt.SetLabel("") + evt.SetEditCanceled(True) + self.GetEventHandler().ProcessEvent(evt) + + + def OnRenameAccept(self, page_index, value): + """ + Called by L{TabTextCtrl}, to accept the changes and to send the + `EVT_AUINOTEBOOK_END_LABEL_EDIT` event. + + :param `page_index`: the page index in the notebook; + :param `value`: the new label for the tab. + """ + + evt = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_END_LABEL_EDIT, self.GetId()) + evt.SetSelection(page_index) + evt.SetEventObject(self) + evt.SetLabel(value) + evt.SetEditCanceled(False) + + return not self.GetEventHandler().ProcessEvent(evt) or evt.IsAllowed() + + + def ResetTextControl(self): + """ Called by L{TabTextCtrl} when it marks itself for deletion. """ + + if not self._textCtrl: + return + + self._textCtrl.Destroy() + self._textCtrl = None + + # tab height might have changed + self.UpdateTabCtrlHeight(force=True) + + + def EditTab(self, page_index): + """ + Starts the editing of an item label, sending a `EVT_AUINOTEBOOK_BEGIN_LABEL_EDIT` event. + + :param `page_index`: the page index we want to edit. + """ + + if page_index >= self._tabs.GetPageCount(): + return False + + if not self.IsRenamable(page_index): + return False + + page_info = self._tabs.GetPage(page_index) + ctrl, ctrl_idx = self.FindTab(page_info.window) + if not ctrl: + return False + + evt = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_BEGIN_LABEL_EDIT, self.GetId()) + evt.SetSelection(page_index) + evt.SetEventObject(self) + if self.GetEventHandler().ProcessEvent(evt) and not evt.IsAllowed(): + # vetoed by user + return False + + if self._textCtrl is not None and page_info != self._textCtrl.item(): + self._textCtrl.StopEditing() + + self._textCtrl = TabTextCtrl(ctrl, page_info, page_index) + self._textCtrl.SetFocus() + + return True diff --git a/agw/aui/auibook.pyc b/agw/aui/auibook.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d84f93f4f2cf7bf7e6678646bd9ebd52b287c4e5 GIT binary patch literal 149599 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 z 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