Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Bundler.py55
-rw-r--r--COPYING339
-rw-r--r--LessonPlanTemplate.txt10
-rw-r--r--MANIFEST27
-rw-r--r--Models.py498
-rw-r--r--ScorePadActivity.py1448
-rw-r--r--Template.py97
-rw-r--r--activity/activity-scorepad.svg24
-rw-r--r--activity/activity.info8
-rw-r--r--db/cnixdb.sqlitebin0 -> 15360 bytes
-rw-r--r--dist/ScorePad-1.xobin0 -> 1006212 bytes
-rw-r--r--images/background.jpgbin0 -> 46247 bytes
-rw-r--r--images/green_button2.pngbin0 -> 1265 bytes
-rw-r--r--images/hello2.pngbin0 -> 46859 bytes
-rw-r--r--images/logo4.pngbin0 -> 67588 bytes
-rw-r--r--images/logo5.pngbin0 -> 53733 bytes
-rw-r--r--images/start.pngbin0 -> 218640 bytes
-rw-r--r--images/wel2.pngbin0 -> 524318 bytes
-rw-r--r--pygtk_chart/__init__.py53
-rw-r--r--pygtk_chart/__init__.pycbin0 -> 1268 bytes
-rw-r--r--pygtk_chart/bar_chart.py729
-rw-r--r--pygtk_chart/bar_chart.pycbin0 -> 26455 bytes
-rw-r--r--pygtk_chart/basics.py133
-rw-r--r--pygtk_chart/basics.pycbin0 -> 4669 bytes
-rw-r--r--pygtk_chart/chart.py592
-rw-r--r--pygtk_chart/chart.pycbin0 -> 22045 bytes
-rw-r--r--pygtk_chart/chart_object.py155
-rw-r--r--pygtk_chart/chart_object.pycbin0 -> 5372 bytes
-rw-r--r--pygtk_chart/data/tango.color7
-rw-r--r--pygtk_chart/label.py744
-rw-r--r--pygtk_chart/label.pycbin0 -> 24623 bytes
-rw-r--r--pygtk_chart/multi_bar_chart.py649
-rw-r--r--pygtk_chart/multi_bar_chart.pycbin0 -> 25001 bytes
-rw-r--r--pygtk_chart/pie_chart.py474
-rwxr-xr-xsetup.py3
35 files changed, 6045 insertions, 0 deletions
diff --git a/Bundler.py b/Bundler.py
new file mode 100644
index 0000000..a00ee14
--- /dev/null
+++ b/Bundler.py
@@ -0,0 +1,55 @@
+from Models import *
+
+class Bundler():
+
+ def bundle_project(self,project):
+
+ project_bundle = "Project|" + str(project.project_id)+"|"+project.title+"|"+\
+ project.author+"|"+project.description+"|"+project.subject+"|"+\
+ str(project.publish_date)+"|"+\
+ str(project.is_owned) + "|"+str(project.is_shared)+"|"+\
+ str(project.rubric_id) +"|"+project.xo_name+"|"+\
+ project.project_sha
+
+ return project_bundle
+
+ def bundle_rubric(self,rubric):
+
+ rubric_bundle = "Rubric|" + str(rubric.rubric_id)+"|"+rubric.title+"|"+\
+ rubric.author+"|"+rubric.description+"|"+\
+ str(rubric.is_predefined) +"|"+rubric.xo_name +"|"+\
+ rubric.rubric_sha
+
+ return rubric_bundle
+
+ def bundle_category(self, categories):
+
+ categorylist = []
+ for category in categories:
+ bundle = "Category|" + str(category.category_id)+"|"+category.name+"|"+\
+ str(category.rubric_id) +"|"+\
+ category.category_sha
+ categorylist.append(bundle)
+
+ return categorylist
+
+ def bundle_level(self, levels):
+
+ levelist = []
+ for level in levels:
+ bundle = "Level|" + str(level.level_id) +"|"+ level.name+"|"+level.description+"|"+\
+ str(level.category_id)+"|"+str(level.rubric_id)+"|"+\
+ level.level_sha
+ levelist.append(bundle)
+
+ return levelist
+
+
+ def bundle_score(self, score):
+
+ score_bundle = "Score|" + str(score.score_id) + "|" + str(score.project_id) + "|" + \
+ str(score.rubric_id) + "|" + str(score.category_id) + "|" + str(score.level_id) + "|" + \
+ score.project_sha + "|" + score.rubric_sha + "|" + score.category_sha + "|" +\
+ score.level_sha + "|" + str(score.count)
+
+ return score_bundle
diff --git a/COPYING b/COPYING
new file mode 100644
index 0000000..d511905
--- /dev/null
+++ b/COPYING
@@ -0,0 +1,339 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users. This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it. (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.) You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+ To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have. You must make sure that they, too, receive or can get the
+source code. And you must show them these terms so they know their
+rights.
+
+ We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+ Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+ Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary. To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ GNU GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License. The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language. (Hereinafter, translation is included without limitation in
+the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+ 1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+ 2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) You must cause the modified files to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ b) You must cause any work that you distribute or publish, that in
+ whole or in part contains or is derived from the Program or any
+ part thereof, to be licensed as a whole at no charge to all third
+ parties under the terms of this License.
+
+ c) If the modified program normally reads commands interactively
+ when run, you must cause it, when started running for such
+ interactive use in the most ordinary way, to print or display an
+ announcement including an appropriate copyright notice and a
+ notice that there is no warranty (or else, saying that you provide
+ a warranty) and that users may redistribute the program under
+ these conditions, and telling the user how to view a copy of this
+ License. (Exception: if the Program itself is interactive but
+ does not normally print such an announcement, your work based on
+ the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+ a) Accompany it with the complete corresponding machine-readable
+ source code, which must be distributed under the terms of Sections
+ 1 and 2 above on a medium customarily used for software interchange; or,
+
+ b) Accompany it with a written offer, valid for at least three
+ years, to give any third party, for a charge no more than your
+ cost of physically performing source distribution, a complete
+ machine-readable copy of the corresponding source code, to be
+ distributed under the terms of Sections 1 and 2 above on a medium
+ customarily used for software interchange; or,
+
+ c) Accompany it with the information you received as to the offer
+ to distribute corresponding source code. (This alternative is
+ allowed only for noncommercial distribution and only if you
+ received the program in object code or executable form with such
+ an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it. For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable. However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License. Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+ 5. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Program or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+ 6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+ 7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all. For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded. In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+ 9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation. If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+ 10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission. For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this. Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+ NO WARRANTY
+
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+ Gnomovision version 69, Copyright (C) year name of author
+ Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+ `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+ <signature of Ty Coon>, 1 April 1989
+ Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs. If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.
diff --git a/LessonPlanTemplate.txt b/LessonPlanTemplate.txt
new file mode 100644
index 0000000..e07bd16
--- /dev/null
+++ b/LessonPlanTemplate.txt
@@ -0,0 +1,10 @@
+Welcome to Scorepad Activity!
+
+The Scorepad activity functions as a tool to share project to peers connected in the network. Other web-based peer assessment tools uses link to view the actual project, but this activity does not support that feature. The actual project output is assumed to be presented already for example a classroom report or presentation, an art project, an essay, etc.. The activity will ask for a project title, author, description, rubric and other essential details. This project instance is sharable to every XO laptop connected to the shared activity using ad-hoc network or mesh connection. This activity is used to assess the project shared by a project owner. The process of peer assessment in this activity is pretty simple because the activity caters elementary students.
+
+1. Add a project
+2. Add a rubric
+3. Share the project
+4. Assess the project
+
+(This page is under construction.)
diff --git a/MANIFEST b/MANIFEST
new file mode 100644
index 0000000..1aeaf1b
--- /dev/null
+++ b/MANIFEST
@@ -0,0 +1,27 @@
+ScorePadActivity.py
+activity/activity.info
+activity/activity-scorepad.svg
+setup.py
+images/hello2.png
+images/green_button2.png
+images/wel2.png
+Template.py
+Models.py
+Bundler.py
+LessonPlanTemplate.txt
+pygtk_chart/data/tango.color
+pygtk_chart/bar_chart.py
+pygtk_chart/basics.py
+pygtk_chart/chart.py
+pygtk_chart/__init__.py
+pygtk_chart/pie_chart.py
+db/cnixdb.sqlite
+images/logo5.png
+images/start.png
+images/logo4.png
+images/background.jpg
+pygtk_chart/chart_object.py
+pygtk_chart/multi_bar_chart.py
+pygtk_chart/label.py
+COPYING
+
diff --git a/Models.py b/Models.py
new file mode 100644
index 0000000..c504886
--- /dev/null
+++ b/Models.py
@@ -0,0 +1,498 @@
+import sqlite3
+import hashlib
+
+class ScorePadDB():
+
+ def __init__(self, nickname = ""):
+
+ #self.scorepaddb = "/home/cnix/ThesisWorkspace2/ScorePad1.4/src/db/scorepaddb.sqlite"
+
+ self.scorepaddb = "db/"+str(nickname)+"db.sqlite"
+ self.connection = sqlite3.connect(self.scorepaddb)
+ self.cursor = self.connection.cursor()
+
+ def create_tables(self):
+ rubric_table = "CREATE TABLE rubric_table (rubric_id INTEGER PRIMARY KEY,\
+ title VARCHAR(40) NOT NULL,\
+ author VARCHAR(40) NOT NULL,\
+ description TEXT,\
+ is_predefined INTEGER,\
+ xo_name VARCHAR(50),\
+ rubric_sha TEXT)"
+ category_table = "CREATE TABLE category_table (category_id INTEGER PRIMARY KEY,\
+ name VARCHAR(40) NOT NULL,\
+ rubric_id INTEGER NOT NULL REFERENCES rubric_table(rubric_id),\
+ category_sha TEXT)"
+
+ level_table = "CREATE TABLE level_table (level_id INTEGER PRIMARY KEY,\
+ name VARCHAR(40) NOT NULL,\
+ description TEXT NOT NULL,\
+ category_id INTEGER NOT NULL REFERENCES category_table(category_id),\
+ rubric_id INTEGER NOT NULL REFERENCES rubric_table(rubric_id),\
+ level_sha TEXT)"
+
+ project_table = "CREATE TABLE project_table (project_id INTEGER PRIMARY KEY,\
+ title VARCHAR(40) NOT NULL,\
+ author VARCHAR(40) NOT NULL,\
+ description TEXT,\
+ subject VARCHAR(40),\
+ publish_date TEXT,\
+ is_owned INTEGER,\
+ is_shared INTEGER,\
+ rubric_id INTEGER NOT NULL REFERENCES rubric_table(rubric_id),\
+ xo_name VARCHAR(50),\
+ project_sha TEXT)"
+
+ score_table = "CREATE TABLE score_table (score_id INTEGER PRIMARY KEY,\
+ project_id INTEGER NOT NULL REFERENCES project_table(project_id),\
+ rubric_id INTEGER NOT NULL REFERENCES rubric_table(rubric_id),\
+ category_id INTEGER NOT NULL REFERENCES category_table(category_id),\
+ level_id INTEGER NOT NULL REFERENCES level_table(level_id),\
+ project_sha TEXT REFERENCES project_table(project_sha),\
+ rubric_sha TEXT REFERENCES rubric_table(rubric_sha),\
+ category_sha TEXT REFERENCES category_table(category_sha),\
+ level_sha TEXT REFERENCES level_table(level_sha),\
+ count INTEGER NOT NULL)"
+
+ assessor_table = "CREATE TABLE assessor_table (assessor_id INTEGER PRIMARY KEY,\
+ name TEXT NOT NULL, \
+ score_id INTEGER NOT NULL REFERENCES score_table(score_id))"
+
+ self.cursor.execute(rubric_table)
+ print "rubric table created"
+ self.cursor.execute(category_table)
+ print "category table created"
+ self.cursor.execute(level_table)
+ print "level table created"
+ self.cursor.execute(project_table)
+ print "project table created"
+ self.cursor.execute(score_table)
+ print "score table created"
+ self.cursor.execute(assessor_table)
+ print "assessor table created"
+ self.cursor.close()
+
+
+ def insert_rubric(self, rubric):
+ temp = (rubric.title, rubric.author, rubric.description, rubric.is_predefined,
+ rubric.xo_name, rubric.rubric_sha)
+ insert_str = "INSERT INTO rubric_table(title, author, description,is_predefined,\
+ xo_name, rubric_sha)\
+ VALUES(?,?,?,?,?,?)"
+ self.cursor.execute(insert_str,temp)
+ self.connection.commit()
+
+ def query_maxrubric(self):
+ query_str = "SELECT MAX(rubric_id) from rubric_table"
+ self.cursor.execute(query_str)
+ rubric_id = self.cursor.fetchone()[0]
+ return rubric_id
+
+ def insert_criteria(self, category, levels):
+ temp = (category.name, category.rubric_id, category.category_sha)
+ insert_str = "INSERT INTO category_table(name,rubric_id,category_sha) VALUES (?,?,?)"
+ self.cursor.execute(insert_str, temp)
+ self.connection.commit()
+
+ query_str = "SELECT MAX(category_id) from category_table"
+ self.cursor.execute(query_str)
+ category_id = self.cursor.fetchone()[0]
+
+ insert_str = "INSERT INTO level_table(name,description,category_id,rubric_id,level_sha)\
+ VALUES(?,?,?,?,?)"
+ for i in range(len(levels)):
+ temp = (levels[i].name, levels[i].description,category_id ,\
+ levels[i].rubric_id, levels[i].level_sha)
+ self.cursor.execute(insert_str, temp)
+ self.connection.commit()
+
+ def insert_project(self, project):
+ temp = (project.title, project.author, project.description, project.subject,\
+ project.publish_date, project.is_owned,\
+ project.is_shared, project.rubric_id,project.xo_name,\
+ project.project_sha)
+
+ insert_str = "INSERT INTO project_table(title, author, description, subject,\
+ publish_date, is_owned, is_shared, rubric_id,xo_name,\
+ project_sha) VALUES (?,?,?,?,?,?,?,?,?,?)"
+
+ self.cursor.execute(insert_str,temp)
+ self.connection.commit()
+
+ def query_maxproject(self):
+ query_str = "SELECT MAX(project_id) from project_table"
+ self.cursor.execute(query_str)
+ project_id = self.cursor.fetchone()[0]
+ return project_id
+
+ def insert_score(self, score):
+ temp = (score.project_id, score.rubric_id, score.category_id, score.level_id,
+ score.project_sha, score.rubric_sha, score.category_sha, score.level_sha, score.count)
+ insert_str = "INSERT INTO score_table(project_id,rubric_id,category_id,level_id,\
+ project_sha, rubric_sha, category_sha, level_sha, count)\
+ VALUES (?,?,?,?,?,?,?,?,?)"
+ self.cursor.execute(insert_str,temp)
+ self.connection.commit()
+
+ def insert_category(self, category):
+ temp = (category.name, category.rubric_id, category.category_sha)
+ insert_str = "INSERT INTO category_table(name,rubric_id,category_sha) VALUES (?,?,?)"
+ self.cursor.execute(insert_str, temp)
+ self.connection.commit()
+
+ def insert_level(self, level):
+ temp = (level.name, level.description, level.category_id, level.rubric_id, level.level_sha)
+ insert_str = "INSERT INTO level_table(name, description, category_id, rubric_id, level_sha) \
+ VALUES(?,?,?,?,?)"
+ self.cursor.execute(insert_str, temp)
+ self.connection.commit()
+
+
+ def query_maxscore(self):
+ query_str = "SELECT MAX(score_id) from score_table"
+ self.cursor.execute(query_str)
+ score_id = self.cursor.fetchone()[0]
+ return score_id
+
+ def query_maxcategory(self):
+ query_str = "SELECT MAX(category_id) from category_table"
+ self.cursor.execute(query_str)
+ category_id = self.cursor.fetchone()[0]
+ return category_id
+
+ def queryall_rubric(self, is_predefined):
+ query_str = "SELECT * from rubric_table where is_predefined = "+str(is_predefined)
+ self.cursor.execute(query_str)
+
+ rubric_list = []
+ for row in self.cursor:
+ rubric = Rubric(row[0], row[1], row[2], row[3], row[4], row[5], row[6])
+ rubric_list.append(rubric)
+ return rubric_list
+
+ def queryall_project(self, is_owned):
+ query_str = "SELECT * from project_table where is_owned = "+str(is_owned)
+ self.cursor.execute(query_str)
+
+ project_list = []
+ for row in self.cursor:
+ project = Project(row[0],row[1],row[2],row[3],row[4],
+ row[5],row[6],row[7],row[8], row[9],row[10])
+ project_list.append(project)
+ return project_list
+
+ def query_rubric(self, rubric_id):
+ query_str = "SELECT * from rubric_table where rubric_id = "+str(rubric_id)
+ self.cursor.execute(query_str)
+
+ for row in self.cursor:
+ rubric = Rubric(row[0],row[1],row[2],row[3],row[4],row[5],row[6])
+
+ return rubric
+
+ def queryall_category(self, rubric_id):
+ query_str = "SELECT * from category_table where rubric_id = "+str(rubric_id)
+ self.cursor.execute(query_str)
+
+ category_list = []
+ for row in self.cursor:
+ category = Category(row[0], row[1], row[2], row[3])
+ category_list.append(category)
+ return category_list
+
+ def query_level(self,category_id):
+
+ query_str = "SELECT * from level_table where category_id ="+str(category_id)
+ self.cursor.execute(query_str)
+
+ level_list = []
+ for row in self.cursor:
+ level = Level(row[0], row[1], row[2], row[3], row[4], row[5])
+ level_list.append(level)
+ return level_list
+
+ def querylevel_id(self, category_id, level_name):
+ temp = (category_id, level_name)
+ query_str = "SELECT level_id from level_table WHERE category_id = ? and name = ?"
+ self.cursor.execute(query_str,temp)
+ level_id = self.cursor.fetchone()[0]
+ return level_id
+
+
+ def query_score(self, project_id, rubric_id, category_id, level_id):
+ temp = (project_id, rubric_id, category_id, level_id)
+ query_str = "SELECT count from score_table WHERE project_id = ? \
+ and rubric_id = ? and category_id = ? and \
+ level_id = ?"
+ self.cursor.execute(query_str, temp)
+ count = self.cursor.fetchone()[0]
+ return count
+
+ def query_score2(self, project_id, rubric_id, category_id, level_id):
+ temp = (project_id, rubric_id, category_id, level_id)
+ query_str = "SELECT * from score_table WHERE project_id = ? \
+ and rubric_id = ? and category_id = ? and \
+ level_id = ?"
+
+ self.cursor.execute(query_str, temp)
+ for row in self.cursor:
+ score = Score(row[0],row[1],row[2],row[3],row[4],row[5],row[6],row[7],row[8],row[9])
+ return score
+
+ def query_project_sha(self, project_id):
+ query_str = "SELECT project_sha from project_table WHERE project_id = "+ str(project_id)
+ self.cursor.execute(query_str)
+
+ project_sha = self.cursor.fetchone()[0]
+ return str(project_sha)
+
+ def query_project(self, project_id):
+ query_str = "SELECT * from project_table WHERE project_id ="+ str(project_id)
+ self.cursor.execute(query_str)
+
+ for row in self.cursor:
+ project = Project(row[0],row[1],row[2],row[3],row[4],row[5],row[6],row[7],row[8],row[9],row[10])
+
+ return project
+
+ def query_rubric_sha(self, rubric_id):
+ query_str = "SELECT rubric_sha from rubric_table WHERE rubric_id = "+ str(rubric_id)
+ self.cursor.execute(query_str)
+
+ rubric_sha = self.cursor.fetchone()[0]
+ return str(rubric_sha)
+
+ def query_category_sha(self, category_id):
+ query_str = "SELECT category_sha from category_table WHERE category_id = "+ str(category_id)
+ self.cursor.execute(query_str)
+
+ category_sha = self.cursor.fetchone()[0]
+ return str(category_sha)
+
+ def query_level_sha(self, level_id):
+ query_str = "SELECT level_sha from level_table WHERE level_id = "+ str(level_id)
+ self.cursor.execute(query_str)
+
+ level_sha = self.cursor.fetchone()[0]
+ return str(level_sha)
+
+ def query_score_id(self, project_sha, rubric_sha, category_sha, level_sha):
+ temp = (project_sha, rubric_sha, category_sha, level_sha)
+ query_str = "SELECT score_id from score_table WHERE project_sha = ? \
+ and rubric_sha = ? and category_sha = ? and level_sha = ?"
+ self.cursor.execute(query_str, temp)
+ score_id = self.cursor.fetchone()[0]
+ return score_id
+
+
+ def update_project(self, modified_project):
+ temp = (modified_project.title,modified_project.author,modified_project.description,\
+ modified_project.subject,\
+ modified_project.publish_date,\
+ modified_project.rubric_id,modified_project.project_sha,\
+ modified_project.project_id)
+ update_str = "UPDATE project_table SET title = ?,"+\
+ "author = ?, "+\
+ "description = ?,"+\
+ "subject = ?,"+\
+ "publish_date = ?,"+\
+ "rubric_id = ?,"+\
+ "project_sha = ?"+\
+ "WHERE project_id = ?"
+ print update_str
+ self.cursor.execute(update_str,temp)
+ self.connection.commit()
+
+
+ def delete_project(self, project_id):
+ delete_str = "DELETE FROM project_table WHERE project_id ="+ str(project_id)
+ self.cursor.execute(delete_str)
+
+ self.connection.commit()
+
+ def score_exists(self, project_id, rubric_id, category_id, level_id):
+ temp = (project_id, rubric_id, category_id, level_id)
+ query_str = "SELECT * FROM score_table WHERE project_id = ? and rubric_id = ? and \
+ category_id = ? and level_id = ?"
+
+ self.cursor.execute(query_str, temp)
+ for row in self.cursor:
+ score = Score(row[0],row[1],row[2],row[3],row[4],row[5],row[6],\
+ row[7],row[8],row[9])
+
+ try:
+ if score.score_id == None:
+ return False
+ else:
+ return True
+ except:
+ return False
+
+ def project_exists(self, project_sha, author):
+ temp = (project_sha, author)
+ query_str = "SELECT * FROM project_table WHERE project_sha = ? and author = ?"
+
+ self.cursor.execute(query_str, temp)
+ for row in self.cursor:
+ project = Project(row[0],row[1],row[2],row[3],row[4],row[5],row[6],\
+ row[7],row[8],row[9],row[10])
+
+ try:
+ if project.project_id == None:
+ return False
+ else:
+ return True
+ except:
+ return False
+
+ def rubric_exists(self, rubric_sha, description):
+ temp = (rubric_sha, description)
+ query_str = "SELECT * FROM rubric_table WHERE rubric_sha = ? and description = ?"
+
+ self.cursor.execute(query_str, temp)
+ for row in self.cursor:
+ rubric = Rubric(row[0],row[1],row[2],row[3],row[4],row[5],row[6])
+
+ try:
+ if rubric.rubric_id == None:
+ return None
+ else:
+ return rubric.rubric_id
+ except:
+ return None
+
+ def query_score_attr(self, score_id):
+ query_str = "SELECT * from score_table WHERE score_id = " + str(score_id)
+ self.cursor.execute(query_str)
+
+ for row in self.cursor:
+ score = Score(row[0],row[1],row[2],row[3],row[4],row[5],row[6],\
+ row[7],row[8],row[9])
+
+ attr = [score.project_id, score.rubric_id, score.category_id, score.level_id]
+ return attr
+
+
+ def increment_scorecount(self, project_id, rubric_id, category_id, level_id):
+ temp = (project_id, rubric_id, category_id, level_id)
+ query_str = "SELECT * FROM score_table WHERE project_id = ? and rubric_id = ? and \
+ category_id = ? and level_id = ?"
+ self.cursor.execute(query_str, temp)
+
+ for row in self.cursor:
+ score = Score(row[0],row[1],row[2],row[3],row[4],row[5],row[6],row[7],row[8],row[9])
+
+ score_id = score.score_id
+ count = score.count
+ count = count +1
+ temp = (count, score_id)
+ update_str = "UPDATE score_table SET count = ?"+\
+ "WHERE score_id = ?"
+ self.cursor.execute(update_str, temp)
+ self.connection.commit()
+ return score_id
+
+
+class Project():
+
+ def __init__(self, project_id = 0, title = "", author = "",
+ description = "", subject = "", publish_date = "",
+ is_owned = 1, is_shared = 1, rubric_id = None, xo_name = "",
+ project_sha = ""):
+ self.project_id = project_id
+ self.title = title
+ self.author = author
+ self.description = description
+ self.subject = subject
+ self.publish_date = publish_date
+ self.is_owned = is_owned
+ self.is_shared = is_shared
+ self.rubric_id = rubric_id
+ self.xo_name = xo_name
+ self.project_sha = self.get_sha(xo_name, title, publish_date)
+
+
+ def get_sha(self, xo_name, title, publish_date):
+ text = xo_name + title + str(publish_date)
+ h = hashlib.sha1()
+ h.update(text)
+ project_sha = str(h.hexdigest())
+
+ return project_sha
+
+
+
+class Rubric():
+
+ def __init__(self, rubric_id = 0, title = "", author = "", description = "",\
+ is_predefined =None, xo_name = "", rubric_sha = ""):
+ self.rubric_id = rubric_id
+ self.title = title
+ self.author = author
+ self.description = description
+ self.is_predefined = is_predefined
+ self.xo_name = xo_name
+ self.rubric_sha = self.get_sha(xo_name, title, author)
+
+ def get_sha(self, xo_name, title, author):
+ text = xo_name + title + author
+ h = hashlib.sha1()
+ h.update(text)
+ rubric_sha = str(h.hexdigest())
+
+ return rubric_sha
+
+
+class Category():
+
+ def __init__(self, category_id = None, name = "", rubric_id = None,
+ category_sha = ""):
+ self.category_id = category_id
+ self.name = name
+ self.rubric_id = rubric_id
+ self.category_sha = self.get_sha(name)
+
+ def get_sha(self, name):
+ text = name
+ h = hashlib.sha1()
+ h.update(text)
+ category_sha = str(h.hexdigest())
+
+ return category_sha
+
+
+class Level():
+
+ def __init__(self, level_id = None, name = "", description = "",
+ category_id = None, rubric_id = None,
+ level_sha = ""):
+ self.level_id = level_id
+ self.name = name
+ self.description = description
+ self.category_id = category_id
+ self.rubric_id = rubric_id
+ self.level_sha = self.get_sha(name,description)
+
+ def get_sha(self, name, description):
+ text = name + description
+ h = hashlib.sha1()
+ h.update(text)
+ level_sha = str(h.hexdigest())
+
+ return level_sha
+
+class Score():
+
+ def __init__(self, score_id = 0, project_id = None, rubric_id = None, category_id = None,level_id = None,
+ project_sha = "", rubric_sha = "", category_sha = "", level_sha = "",count = 0):
+ self.score_id = score_id
+ self.project_id = project_id
+ self.rubric_id = rubric_id
+ self.category_id = category_id
+ self.level_id = level_id
+ self.project_sha = project_sha
+ self.rubric_sha = rubric_sha
+ self.category_sha = category_sha
+ self.level_sha = level_sha
+ self.count = count
+
diff --git a/ScorePadActivity.py b/ScorePadActivity.py
new file mode 100644
index 0000000..b4e798d
--- /dev/null
+++ b/ScorePadActivity.py
@@ -0,0 +1,1448 @@
+# Copyright 2011-2012 Almira Cayetano <almiracayetano@gmail.com>
+# Copyright 2011-2012 Christian Joy Aranas <cjmaranas@gmail.com>
+# Copyright 2011-2012 Ma. Rowena Solamo <rcsolamo@dcs.upd.edu.ph>
+# Copyright 2011-2012 Rommel Feria <rpferia@dcs.upd.edu.ph>
+#
+# University of the Philippines
+# College of Engineering
+# Department of Computer Science
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+#
+# Copyright 2007-2008 One Laptop Per Child Chat Activity for the method on
+# connecting sugar activities
+# Copyright 2008 Sven Festersen for the pygtk_chart library
+
+
+import pygtk
+pygtk.require('2.0')
+import gtk, gobject
+from Models import *
+from Template import Template
+import datetime
+import pango
+from Bundler import Bundler
+from pygtk_chart import multi_bar_chart
+
+import cjson
+import logging
+import telepathy
+from dbus.service import method, signal
+from dbus.gobject_service import ExportedGObject
+
+from sugar.activity.activity import Activity, ActivityToolbox
+from sugar.activity import activity
+from sugar.graphics.alert import NotifyAlert
+from sugar.presence import presenceservice
+from sugar.presence.tubeconn import TubeConnection
+
+from telepathy.interfaces import (
+ CHANNEL_INTERFACE, CHANNEL_INTERFACE_GROUP, CHANNEL_TYPE_TEXT,
+ CONN_INTERFACE_ALIASING)
+from telepathy.constants import (
+ CHANNEL_GROUP_FLAG_CHANNEL_SPECIFIC_HANDLES,
+ CHANNEL_TEXT_MESSAGE_TYPE_NORMAL)
+from telepathy.client import Connection, Channel
+
+SERVICE = "org.laptop.ScorePad"
+IFACE = SERVICE
+PATH = "/org/laptop/ScorePad"
+
+BORDER_COLOR = '#FFDE00'
+BACKGROUND_COLOR = '#66CC00'
+BUTTON_COLOR = '#097054'
+WHITE = '#FFFFFF'
+BLUE = '#82CAFA'
+
+lessonplan_path = 'LessonPlanTemplate.txt'
+
+PROJECTLIST = []
+TITLELIST = []
+FRIENDSTITLELIST = []
+FRIENDSPROJECTLIST = []
+RUBRICLIST = []
+RUBRICTITLE = []
+OTHER_RUBRICLIST = []
+OTHER_RUBRICTITLE = []
+
+
+def xpm_label_box(xpm_filename, label_text):
+ box1 = gtk.HBox(False, 0)
+
+ image = gtk.Image()
+ image.set_from_file(xpm_filename)
+
+ label = gtk.Label(label_text)
+
+ box1.pack_start(image, False, False, 0)
+ if label_text != "":
+ box1.pack_start(label, False, False, 3)
+
+ image.show()
+ label.show()
+
+ return box1
+
+def theme_button(button):
+
+ button.modify_bg(gtk.STATE_NORMAL, gtk.gdk.color_parse(BUTTON_COLOR))
+
+ return button
+
+def theme_box(box, color):
+ eb = gtk.EventBox()
+ box.set_border_width(5)
+ eb.modify_bg(gtk.STATE_NORMAL, gtk.gdk.Color(color))
+ eb.add(box)
+
+ return eb
+
+class ScorePadActivity(activity.Activity):
+
+ def __init__(self, handle):
+ Activity.__init__(self, handle)
+ self.set_title('ScorePad Activity')
+
+ toolbox = ActivityToolbox(self)
+ self.set_toolbox(toolbox)
+ toolbox.show()
+
+ self.pservice = presenceservice.get_instance()
+
+ owner = self.pservice.get_owner()
+ self.owner = owner
+ self.owner_nick = owner.props.nick
+
+ main_canvas = self.build_root()
+ self.set_canvas(main_canvas)
+ main_canvas.show_all()
+
+ self.is_shared = False
+ self.text_channel = None
+ if self.shared_activity:
+ self.connect('joined', self._joined_cb)
+ if self.get_shared():
+ self._joined_cb(self)
+ else:
+ self.connect('shared', self._shared_cb)
+
+ def build_root(self):
+ self.scorepadDB = ScorePadDB(self.owner_nick)
+ try:
+ self.scorepadDB.create_tables()
+ template = Template(self.owner_nick)
+ template.save_template()
+ except:
+ print "tables exist"
+
+ list = self.scorepadDB.queryall_rubric(1)
+ for temp in list:
+ RUBRICLIST.append(temp)
+ RUBRICTITLE.append(temp.title)
+
+ list = self.scorepadDB.queryall_rubric(0)
+ for temp in list:
+ OTHER_RUBRICLIST.append(temp)
+ OTHER_RUBRICTITLE.append(temp.title)
+
+ list = self.scorepadDB.queryall_project(1)
+ for temp in list:
+ PROJECTLIST.append(temp)
+ TITLELIST.append(temp.title)
+
+ list = self.scorepadDB.queryall_project(0)
+ for temp in list:
+ FRIENDSPROJECTLIST.append(temp)
+ FRIENDSTITLELIST.append(temp.title)
+ print "friends :" +str(temp.title)
+
+ self.is_owned = True
+ self.category_titles = [] #submit grades
+ self.is_exists = False
+
+ self.main_table = gtk.HPaned()
+ self.main_table.set_position(270)
+ self.main_table.modify_bg(gtk.STATE_NORMAL, gtk.gdk.Color(BACKGROUND_COLOR))
+ self.main_table.modify_bg(gtk.STATE_ACTIVE, gtk.gdk.Color(BACKGROUND_COLOR))
+ menupanel = self.build_menupanel()
+ self.main_table.add1(menupanel)
+
+ self.build_startpage("Start Page")
+
+ main_table_eb = theme_box(self.main_table, BACKGROUND_COLOR)
+
+ return main_table_eb
+
+ def build_menupanel(self):
+ menupanel = gtk.Table(2,1, True)
+ menupanel.set_border_width(2)
+
+ logo = gtk.Image()
+ logo.set_from_file("images/hello2.png")
+ logo.show()
+
+ project_button = gtk.Button("Projects")
+ project_button = theme_button(project_button)
+ project_button.connect("clicked", self.project_cb, "Projects")
+ create_button = gtk.Button("Create New Project")
+ create_button = theme_button(create_button)
+ create_button.connect("clicked", self.addproject_cb, "Create new project")
+ rubrics_button = gtk.Button("Rubrics")
+ rubrics_button = theme_button(rubrics_button)
+ rubrics_button.connect("clicked", self.rubrics_cb, "Rubrics")
+ lessonplan_button = gtk.Button("Lesson Plan")
+ lessonplan_button = theme_button(lessonplan_button)
+ lessonplan_button.connect("clicked", self.lessonplan_cb)
+ tutorial_button = gtk.Button("Tutorial")
+ tutorial_button = theme_button(tutorial_button)
+ tutorial_button.connect("clicked", self.tutorial_cb)
+
+ button_array = [project_button, create_button, rubrics_button, lessonplan_button, tutorial_button]
+
+ menupanel.attach(logo,0,1,0,1)
+ menu_table_box = gtk.VBox(False,0)
+ for i in range(5) :
+ button = theme_box(button_array[i], BORDER_COLOR)
+ menu_table_box.add(button)
+ menupanel.attach(menu_table_box,0,1,1,2)
+
+ return menupanel
+
+ def build_startpage(self,label):
+ self.build_processframe(label)
+
+ logo = gtk.Image()
+ logo.set_from_file("images/wel2.png")
+
+ self.processtable.attach(logo, 0,2,0,12)
+ self.main_table.show_all()
+
+ def build_processframe(self,label):
+ self.processtable = gtk.Table(12,2,True)
+ self.processpanel = gtk.Frame()
+ self.processpanel.add(self.processtable)
+ self.processpanel.set_label(label)
+ self.main_table.add2(self.processpanel)
+ self.main_table.show_all()
+
+
+ def addproject_cb(self,widget,label):
+ entries = self.build_projectentry(label)
+ finalize_button = gtk.Button(" Finalize ")
+ finalize_button = theme_button(finalize_button)
+ finalize_button.connect("clicked", self.finalize_cb, entries)
+ box = gtk.HBox(False,0)
+ box.pack_start(finalize_button,False,False,0)
+ box = theme_box(box, BORDER_COLOR)
+ self.processtable.attach(box, 0,3, 11,12)
+ self.main_table.show_all()
+
+ def build_projectentry(self,label):
+ self.processpanel.destroy()
+ self.build_processframe(label)
+ title_label = gtk.Label("Project Title")
+ author_label = gtk.Label("Author")
+ description_label = gtk.Label("Project Description")
+ subject_label = gtk.Label("Subject")
+ date_label = gtk.Label("Date")
+ rubric_label = gtk.Label("Rubric Name")
+
+ title = gtk.Entry(50)
+ author = gtk.Entry(50)
+ description = gtk.TextView()
+ description.set_wrap_mode(gtk.WRAP_WORD)
+ description.modify_base(gtk.STATE_NORMAL, gtk.gdk.Color(BLUE))
+ description.set_border_width(3)
+ subject = gtk.Entry(50)
+ publish_date = gtk.Entry(50)
+ rubric_combobox = gtk.ComboBox()
+ rubric_list = gtk.ListStore(str)
+ cell = gtk.CellRendererText()
+ rubric_combobox.pack_start(cell)
+ rubric_combobox.add_attribute(cell, 'text', 0)
+
+ today = datetime.date.today()
+ publish_date.set_text(str(today))
+
+ for i in range(len(RUBRICTITLE)):
+ rubric_list.append([RUBRICTITLE[i]])
+ rubric_combobox.set_model(rubric_list)
+ rubric_combobox.set_active(0)
+
+ label = [title_label, author_label,description_label,subject_label,date_label,rubric_label]
+ entries = [title, author, description,subject,publish_date,rubric_combobox]
+ j = 0
+ for i in range(6):
+ if j == 2:
+ self.processtable.attach(label[i],0,1,j,5)
+ entry = entries[i]
+ entry.modify_bg(gtk.STATE_NORMAL, gtk.gdk.Color(BUTTON_COLOR))
+ self.processtable.attach(entries[i],1,3,j,5)
+ j = 4
+ else :
+ if i == 5:
+ self.processtable.attach(label[i],0,1,j,j+1)
+ entry = entries[i]
+ entry.modify_bg(gtk.STATE_NORMAL,gtk.gdk.Color(BORDER_COLOR))
+ self.processtable.attach(entry,1,3,j,j+1)
+ else :
+ self.processtable.attach(label[i],0,1,j,j+1)
+ entry = entries[i]
+ entry.modify_bg(gtk.STATE_NORMAL,gtk.gdk.Color(BORDER_COLOR))
+ self.processtable.attach(entries[i],1,3,j,j+1)
+ j = j+1
+
+ return entries
+
+ def finalize_cb(self,widget,entries):
+ project = self.get_entries(entries)
+
+ if self.scorepadDB.project_exists(project.project_sha, project.author):
+ md = gtk.MessageDialog(parent = None, buttons = gtk.BUTTONS_OK, \
+ flags = gtk.DIALOG_DESTROY_WITH_PARENT, \
+ type = gtk.MESSAGE_INFO,\
+ message_format = "Error. Duplicated project.")
+ md.run()
+ md.destroy()
+ else:
+ self.scorepadDB.insert_project(project)
+ project_id = self.scorepadDB.query_maxproject()
+ project.project_id = project_id
+
+ rubric_id = project.rubric_id
+ rubric = self.scorepadDB.query_rubric(rubric_id)
+ categories = self.scorepadDB.queryall_category(rubric_id)
+ self.initialize_score(project, rubric, categories)
+
+ TITLELIST.append(project.title)
+ PROJECTLIST.append(project)
+ self.processpanel.destroy()
+ self.build_processframe("Projects")
+ tuplelist1 = (TITLELIST, PROJECTLIST)
+ tuplelist2 = (FRIENDSTITLELIST, FRIENDSPROJECTLIST)
+ projectlist = self.build_projectpanel(tuplelist1, tuplelist2)
+ self.processtable.attach(projectlist,0,2,0,11)
+ actionpanel = self.build_project_actionpanel()
+ self.processtable.attach(actionpanel,0,2,11,12)
+ self.main_table.show_all()
+
+ def editproject_cb(self, widget, label):
+ try:
+ if self.is_owned:
+ project = PROJECTLIST[self.selected_project]
+
+ entries = self.build_projectentry(label)
+ update_button = gtk.Button(" Update ")
+ update_button = theme_button(update_button)
+ update_button.connect("clicked", self.update_cb, entries)
+ box = gtk.HBox(False,0)
+ box.pack_start(update_button, False,False,0)
+ box = theme_box(box,BORDER_COLOR)
+ self.processtable.attach(box, 0,3, 11,12)
+ self.set_entries(entries, project)
+ self.main_table.show_all()
+ else:
+ md = gtk.MessageDialog(parent = None, buttons = gtk.BUTTONS_OK, \
+ flags = gtk.DIALOG_DESTROY_WITH_PARENT, \
+ type = gtk.MESSAGE_INFO,\
+ message_format = "Sorry. You cannot edit your friend's project.")
+ md.run()
+ md.destroy()
+ except:
+ md = gtk.MessageDialog(parent = None, buttons = gtk.BUTTONS_OK, \
+ flags = gtk.DIALOG_DESTROY_WITH_PARENT, \
+ type = gtk.MESSAGE_INFO,\
+ message_format = "Error. No project selected.")
+ md.run()
+ md.destroy()
+
+ def update_cb(self,widget,entries):
+ modified_project = self.get_entries(entries)
+
+ project = PROJECTLIST[self.selected_project]
+ modified_project.project_id = project.project_id
+ self.scorepadDB.update_project(modified_project)
+
+ TITLELIST[self.selected_project] = modified_project.title
+ PROJECTLIST[self.selected_project] = modified_project
+ self.processpanel.destroy()
+ self.build_processframe("Projects")
+ tuplelist1 = (TITLELIST, PROJECTLIST)
+ tuplelist2 = (FRIENDSTITLELIST, FRIENDSPROJECTLIST)
+ projectlist = self.build_projectpanel(tuplelist1, tuplelist2)
+ self.processtable.attach(projectlist,0,2,0,11)
+ actionpanel = self.build_project_actionpanel()
+ self.processtable.attach(actionpanel,0,2,11,12)
+ self.main_table.show_all()
+
+ def set_entries(self,entries,project):
+ entries[0].set_text(project.title)
+ entries[1].set_text(project.author)
+ buffer = entries[2].get_buffer()
+ buffer.set_text(project.description)
+ entries[3].set_text(project.subject)
+
+ rubric = self.scorepadDB.query_rubric(project.rubric_id)
+
+ for i in range(len(RUBRICTITLE)):
+ if str(rubric.title) == str(RUBRICTITLE[i]):
+ active_rubric = i
+
+ entries[4].set_text(project.publish_date)
+ entries[5].set_active(active_rubric)
+
+ def get_entries(self,entries):
+
+ title = entries[0].get_text()
+ author = entries[1].get_text()
+ description_buffer = entries[2].get_buffer()
+ start = description_buffer.get_start_iter()
+ end = description_buffer.get_end_iter()
+ description = description_buffer.get_text(start,end,True)
+ subject = entries[3].get_text()
+ publish_date = entries[4].get_text()
+ model = entries[5].get_model()
+ chosen_rubric = entries[5].get_active()
+ rubric = model[chosen_rubric][0]
+
+ for r in RUBRICLIST:
+ if str(r.title) == str(rubric):
+ rubric_id = r.rubric_id
+
+ is_shared = 0
+ is_owned = 1
+
+ project = Project(None,title,author,description,subject,publish_date,is_owned,is_shared,rubric_id,
+ self.owner_nick,"")
+
+ return project
+
+ def viewproject_details(self,widget,row,col,tuple):
+ buffer = tuple[0]
+ projectlist = tuple[1]
+ buffer.set_text("")
+ r = row[0]
+ self.selected_project = r
+ self.is_owned = tuple[2]
+ print self.selected_project
+ project = projectlist[r]
+ print "rubric id : "+str(project.rubric_id)
+ print "project id :"+str(project.project_id)
+ rubric = self.scorepadDB.query_rubric(project.rubric_id)
+
+ details ="===PROJECT DETAILS===\n\n"+"Title :\n -"+project.title+"\n"+\
+ "Author :\n -"+project.author+"\n"+\
+ "Description :\n -"+project.description+"\n"+\
+ "Subject :\n -"+project.subject+"\n"+\
+ "Date :\n -"+str(project.publish_date)+"\n"+\
+ "Rubric :\n -"+rubric.title+"\n"
+
+ buffer.set_text(details)
+
+ def deleteproject_cb(self,widget,data=None):
+
+ try:
+ if self.is_owned == True:
+ project_id = PROJECTLIST[self.selected_project].project_id
+ else:
+ project_id = FRIENDSPROJECTLIST[self.selected_project].project_id
+
+ warning = gtk.MessageDialog(parent = None,buttons = gtk.BUTTONS_YES_NO, \
+ flags =gtk.DIALOG_DESTROY_WITH_PARENT,\
+ type = gtk.MESSAGE_WARNING,\
+ message_format = "Are you sure you want to delete the project?")
+ result = warning.run()
+
+ if result == gtk.RESPONSE_YES:
+ print "Yes was clicked"
+ if self.is_owned == True:
+ self.scorepadDB.delete_project(project_id)
+ TITLELIST.remove(TITLELIST[self.selected_project])
+ PROJECTLIST.remove(PROJECTLIST[self.selected_project])
+ else:
+ self.scorepadDB.delete_project(project_id)
+ FRIENDSTITLELIST.remove(TITLELIST[self.selected_project])
+ FRIENDSPROJECTLIST.remove(PROJECTLIST[self.selected_project])
+ warning.destroy()
+ elif result == gtk.RESPONSE_NO:
+ print "No was clicked"
+ warning.destroy()
+
+ self.processpanel.destroy()
+ self.build_processframe("Projects")
+ tuplelist1 = (TITLELIST, PROJECTLIST)
+ tuplelist2 = (FRIENDSTITLELIST, FRIENDSPROJECTLIST)
+ projectlist = self.build_projectpanel(tuplelist1, tuplelist2)
+ self.processtable.attach(projectlist,0,2,0,11)
+ actionpanel = self.build_project_actionpanel()
+ self.processtable.attach(actionpanel,0,2,11,12)
+ self.main_table.show_all()
+ except:
+ md = gtk.MessageDialog(parent = None, buttons = gtk.BUTTONS_OK, \
+ flags = gtk.DIALOG_DESTROY_WITH_PARENT, \
+ type = gtk.MESSAGE_INFO,\
+ message_format = "Error. No project selected.")
+ md.run()
+ md.destroy()
+
+ def share_cb(self,widget,data=None):
+
+ if self.is_shared :
+ if self.is_owned:
+ project = PROJECTLIST[self.selected_project]
+ print "selected " + str(self.selected_project)
+ bundler = Bundler()
+ project_bundle = bundler.bundle_project(project)
+ rubric_id = project.rubric_id
+ rubric = self.scorepadDB.query_rubric(rubric_id)
+ rubric_bundle = bundler.bundle_rubric(rubric)
+ categories = self.scorepadDB.queryall_category(rubric_id)
+ category_bundle = bundler.bundle_category(categories)
+ level_bundle_list = []
+ for category in categories:
+ levels = self.scorepadDB.query_level(category.category_id)
+ level_bundle = bundler.bundle_level(levels)
+ level_bundle_list.append(level_bundle)
+
+ self.sendbundle_cb(rubric_bundle)
+ self.sendbundle_cb(project_bundle)
+ for i in range(len(category_bundle)):
+ self.sendbundle_cb(category_bundle[i])
+ level_temp = level_bundle_list[i]
+ for level in level_temp:
+ self.sendbundle_cb(level)
+ self.currently_shared = project.project_sha
+ md = gtk.MessageDialog(parent = None, buttons = gtk.BUTTONS_OK, \
+ flags = gtk.DIALOG_DESTROY_WITH_PARENT, \
+ type = gtk.MESSAGE_INFO,\
+ message_format = "Project shared")
+ md.run()
+ md.destroy()
+ else:
+ md = gtk.MessageDialog(parent = None, buttons = gtk.BUTTONS_OK, \
+ flags = gtk.DIALOG_DESTROY_WITH_PARENT, \
+ type = gtk.MESSAGE_INFO,\
+ message_format = "Cannot send other's project")
+ md.run()
+ md.destroy()
+ else:
+ md = gtk.MessageDialog(parent = None, buttons = gtk.BUTTONS_OK, \
+ flags = gtk.DIALOG_DESTROY_WITH_PARENT, \
+ type = gtk.MESSAGE_INFO,\
+ message_format = "Cannot share. You are not connected to anybody.")
+ md.run()
+ md.destroy()
+
+ def project_cb(self,widget,label):
+ self.processpanel.destroy()
+ self.build_processframe(label)
+
+ tuplelist1 = (TITLELIST, PROJECTLIST)
+ tuplelist2 = (FRIENDSTITLELIST, FRIENDSPROJECTLIST)
+
+ panel = self.build_projectpanel(tuplelist1, tuplelist2)
+ panel.modify_bg(gtk.STATE_NORMAL, gtk.gdk.Color(BACKGROUND_COLOR))
+ panel.set_border_width(5)
+ self.processtable.attach(panel,0,2,0,11)
+
+ actionpanel = self.build_project_actionpanel()
+ self.processtable.attach(actionpanel,0,2,11,12)
+
+ self.main_table.show_all()
+
+ def build_projectpanel(self, tuplelist1, tuplelist2):
+ vpanel = gtk.VPaned()
+ vpanel.modify_bg(gtk.STATE_NORMAL, gtk.gdk.Color(BACKGROUND_COLOR))
+ vpanel.modify_bg(gtk.STATE_ACTIVE, gtk.gdk.Color(BACKGROUND_COLOR))
+ panel = gtk.HPaned()
+
+ mytitlelist = tuplelist1[0]
+ myprojectlist = tuplelist1[1]
+ friendstitlelist = tuplelist2[0]
+ friendsprojectlist = tuplelist2[1]
+
+ tree_store1 = gtk.TreeStore(gobject.TYPE_STRING, gobject.TYPE_BOOLEAN)
+
+ for project in mytitlelist:
+ tree_store1.append(None, (project,None))
+ tree_view1 = gtk.TreeView(tree_store1)
+ tree_view1.modify_base(gtk.STATE_NORMAL, gtk.gdk.color_parse(BLUE))
+ tree_view1.set_rules_hint(True)
+ renderer1 = gtk.CellRendererText()
+ project_column1 = gtk.TreeViewColumn("Projects", renderer1, text = 0)
+
+ tree_view1.append_column(project_column1)
+ tree_view1_scroll = gtk.ScrolledWindow()
+ tree_view1_scroll.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
+ tree_view1_scroll.add(tree_view1)
+ vpanel.add1(tree_view1_scroll)
+
+ tree_store2 = gtk.TreeStore(gobject.TYPE_STRING, gobject.TYPE_BOOLEAN)
+
+ for friendsproject in friendstitlelist:
+ tree_store2.append(None, (friendsproject,None))
+ tree_view2 = gtk.TreeView(tree_store2)
+ tree_view2.modify_base(gtk.STATE_NORMAL, gtk.gdk.color_parse(BLUE))
+ tree_view2.set_rules_hint(True)
+ renderer2 = gtk.CellRendererText()
+ project_column2 = gtk.TreeViewColumn("Friends Projects", renderer2, text = 0)
+ tree_view2.append_column(project_column2)
+
+ tree_view2_scroll = gtk.ScrolledWindow()
+ tree_view2_scroll.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
+ tree_view2_scroll.add(tree_view2)
+
+ vpanel.set_position(300)
+ vpanel.add2(tree_view2_scroll)
+
+ sw = gtk.ScrolledWindow()
+ sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
+ project_view = gtk.TextView()
+ project_view.set_wrap_mode(gtk.WRAP_WORD)
+ project_view.modify_base(gtk.STATE_NORMAL, gtk.gdk.Color(BLUE))
+ buffer = project_view.get_buffer()
+
+ sw.add(project_view)
+ sw = theme_box(sw, BUTTON_COLOR)
+
+ panel.modify_bg(gtk.STATE_NORMAL, gtk.gdk.Color(BACKGROUND_COLOR))
+ panel.modify_base(gtk.STATE_NORMAL, gtk.gdk.Color(BLUE))
+ panel.set_position(410)
+ panel.add1(vpanel)
+ panel.add2(sw)
+
+ tuple1 = (buffer, myprojectlist,True)
+ tuple2 = (buffer, friendsprojectlist,False)
+
+ tree_view1.connect("row-activated", self.viewproject_details, tuple1)
+ tree_view2.connect("row-activated", self.viewproject_details, tuple2)
+
+ return panel
+
+ def build_project_actionpanel(self):
+ actionpanel = gtk.HBox(False, 20)
+ leftpanel = gtk.HBox(False,5)
+ rightpanel = gtk.HBox(False,5)
+ edit_button = gtk.Button("Edit")
+ edit_button = theme_button(edit_button)
+ edit_button.connect("clicked", self.editproject_cb, "Edit Project")
+ share_button = gtk.Button("Share")
+ share_button = theme_button(share_button)
+ share_button.connect("clicked", self.share_cb)
+ evaluate_button = gtk.Button("Evaluate")
+ evaluate_button = theme_button(evaluate_button)
+ evaluate_button.connect("clicked", self.evaluate_cb)
+ grades_button = gtk.Button("See Grades")
+ grades_button = theme_button(grades_button)
+ grades_button.connect("clicked", self.seegrades_cb)
+ delete_button = gtk.Button("Delete")
+ delete_button = theme_button(delete_button)
+ delete_button.connect("clicked", self.deleteproject_cb)
+
+ leftpanel.add(edit_button)
+ leftpanel.add(share_button)
+ rightpanel.add(evaluate_button)
+ rightpanel.add(grades_button)
+ rightpanel.add(delete_button)
+ leftpanel = theme_box(leftpanel, BORDER_COLOR)
+ rightpanel = theme_box(rightpanel, BORDER_COLOR)
+ actionpanel.add(leftpanel)
+ actionpanel.add(rightpanel)
+ actionpanel.set_border_width(5)
+
+ return actionpanel
+
+ def evaluate_cb(self, widget):
+ try:
+ if self.is_owned == True:
+ project = PROJECTLIST[self.selected_project]
+ else:
+ project = FRIENDSPROJECTLIST[self.selected_project]
+
+ rubric = self.scorepadDB.query_rubric(project.rubric_id)
+ self.processpanel.destroy()
+ self.build_processframe(project.title+" by "+project.author)
+
+ upperbox = self.build_upper_evalbox(project)
+ upperbox.modify_bg(gtk.STATE_NORMAL, gtk.gdk.Color(BACKGROUND_COLOR))
+
+ self.processtable.attach(upperbox,0,2,0,2)
+
+ lowerbox = gtk.VPaned()
+ lowerbox.modify_bg(gtk.STATE_NORMAL, gtk.gdk.Color(BACKGROUND_COLOR))
+ lowerbox.modify_bg(gtk.STATE_ACTIVE, gtk.gdk.Color(BACKGROUND_COLOR))
+ rubricsbox = self.build_lower_evalbox(rubric)
+
+ sw = gtk.ScrolledWindow()
+ sw.set_policy(gtk.POLICY_AUTOMATIC,gtk.POLICY_AUTOMATIC)
+ sw.add_with_viewport(rubricsbox)
+ sw = theme_box(sw, BUTTON_COLOR)
+ sw.set_border_width(5)
+ lowerbox.add1(sw)
+
+ sw2 = gtk.ScrolledWindow()
+ sw2.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
+ self.level_view = gtk.TextView()
+ self.level_view.set_wrap_mode(gtk.WRAP_WORD)
+ self.level_view.modify_base(gtk.STATE_NORMAL, gtk.gdk.Color(BLUE))
+ buffer1 = self.level_view.get_buffer()
+ buffer1.set_text("Rubric description")
+ sw2.add(self.level_view)
+ sw2 = theme_box(sw2, BUTTON_COLOR)
+
+ lowerbox.set_position(380)
+ lowerbox.add2(sw2)
+
+ tuple = [rubric, project]
+ box = gtk.HBox(False,0)
+ submit_button = gtk.Button(" Submit ")
+ submit_button = theme_button(submit_button)
+ submit_button.connect("clicked", self.submitgrade_cb, tuple)
+ box.pack_start(submit_button, False, False, 0)
+ box = theme_box(box, BORDER_COLOR)
+
+ self.processtable.attach(lowerbox,0,2,2,11)
+ self.processtable.attach(box,0,2,11,12)
+ self.main_table.show_all()
+ except:
+ md = gtk.MessageDialog(parent = None, buttons = gtk.BUTTONS_OK, \
+ flags = gtk.DIALOG_DESTROY_WITH_PARENT, \
+ type = gtk.MESSAGE_INFO,\
+ message_format = "Error. No project selected.")
+ md.run()
+ md.destroy()
+
+ def build_upper_evalbox(self,project):
+ hpaned = gtk.HPaned()
+ leftbox = gtk.Table(2,1,False)
+ sw = gtk.ScrolledWindow()
+ sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
+ description = gtk.TextView()
+ description.set_wrap_mode(gtk.WRAP_WORD)
+ description.modify_base(gtk.STATE_NORMAL, gtk.gdk.Color(BLUE))
+ buffer = description.get_buffer()
+ buffer.set_text(project.description)
+ sw.set_border_width(3)
+ sw.add(description)
+
+ leftbox.attach(sw,0,1,0,2)
+
+ hpaned.modify_bg(gtk.STATE_NORMAL, gtk.gdk.Color(BACKGROUND_COLOR))
+ hpaned.modify_bg(gtk.STATE_ACTIVE, gtk.gdk.Color(BACKGROUND_COLOR))
+ hpaned.set_position(450)
+ leftbox = theme_box(leftbox, BORDER_COLOR)
+ hpaned.add1(leftbox)
+
+ rightbox = gtk.VBox(False,0)
+ rubric = self.scorepadDB.query_rubric(project.rubric_id)
+ rubric_label = gtk.Label("Rubric")
+ rubric_entry = gtk.Entry()
+ rubric_entry.set_text(rubric.title)
+ rbox = gtk.HBox()
+ rbox.add(rubric_label)
+ rbox.add(rubric_entry)
+ rightbox.add(rbox)
+ date_label = gtk.Label("Date")
+ date_entry = gtk.Entry()
+ date_entry.set_text(project.publish_date)
+ dbox = gtk.HBox()
+ dbox.add(date_label)
+ dbox.add(date_entry)
+ rightbox.add(dbox)
+ rightbox = theme_box(rightbox, BORDER_COLOR)
+ hpaned.add2(rightbox)
+
+ hpaned.set_border_width(5)
+
+ return hpaned
+
+ def build_lower_evalbox(self, rubric):
+ rubric_id = rubric.rubric_id
+ category = self.scorepadDB.queryall_category(rubric_id)
+ row = len(category)+1
+ category_id = category[0].category_id
+ column = len(self.scorepadDB.query_level(category_id))+1
+
+ column_names = self.scorepadDB.query_level(category_id)
+
+ levels = []
+
+ for i in range(column-1):
+ level = []
+ for j in range(row-1):
+ category_id = category[j].category_id
+ level_temp = self.scorepadDB.query_level(category_id)
+ level.append(level_temp[i].description)
+ levels.append(level)
+
+ view = self.build_category_column(rubric, levels, column_names)
+ return view
+
+ def build_category_column(self, rubric, levels,column_names):
+ self.tree_store = gtk.TreeStore( gobject.TYPE_STRING,gobject.TYPE_BOOLEAN,gobject.TYPE_BOOLEAN,
+ gobject.TYPE_BOOLEAN,gobject.TYPE_BOOLEAN)
+
+ column = [None, None, None, None]
+
+ for i in range(len(levels)):
+ column[i] = levels[i]
+
+ rubric_id = rubric.rubric_id
+ category = self.scorepadDB.queryall_category(rubric_id)
+
+ for i in range(len(category)):
+ self.tree_store.append( None, (category[i].name,
+ None, None,
+ None, None) )
+
+ view = gtk.TreeView(self.tree_store)
+ view.modify_base(gtk.STATE_NORMAL, gtk.gdk.color_parse(BLUE))
+ view.set_rules_hint(True)
+ renderer = gtk.CellRendererText()
+ column0 = gtk.TreeViewColumn("Category", renderer, text=0)
+ column0.set_resizable(True)
+ view.append_column(column0)
+ view.connect("row-activated", self.viewlevel_details, rubric_id, category)
+
+ renderlist = []
+ self.number_of_level = len(levels)
+ for i in range(len(levels)):
+ render = gtk.CellRendererToggle()
+ render.set_property('indicator-size', 20)
+ render.set_property('activatable', True)
+ render.connect( 'toggled', self.col1_toggled_cb, i+1 )
+ renderlist.append(render)
+ column = gtk.TreeViewColumn(column_names[i].name, renderlist[i])
+ column.add_attribute( renderlist[i], "active", i+1)
+ column.set_resizable(True)
+ view.append_column(column)
+
+ return view
+
+ def col1_toggled_cb( self, cell, path, col ):
+
+ for i in range(self.number_of_level+1):
+ if ( (self.tree_store[path][i] == True) and (i!=col) ):
+ self.tree_store[path][i] = False
+
+ self.tree_store[path][col] = not self.tree_store[path][col]
+ return
+
+ def viewlevel_details(self, widget, row, col, rubric_id, categories):
+
+ selected_row = row[0]
+ category_id = categories[selected_row].category_id
+ category_name = categories[selected_row].name
+ levels = self.scorepadDB.query_level(category_id)
+ buffer = self.level_view.get_buffer()
+ buffer.set_text("")
+
+ details = "===" + str(category_name)+ "===\n\n"
+ for level in levels:
+ details = details + level.name + "\n" + \
+ " - " + level.description + "\n\n"
+ buffer.set_text(details)
+
+
+ def lessonplan_cb(self,widget,data= None):
+ self.processpanel.destroy()
+ self.build_processframe("Lesson Plan")
+
+ update_lesson = gtk.Button(" Update ")
+ update_lesson = theme_button(update_lesson)
+
+ hbox = gtk.HBox(False,0)
+ box = gtk.HBox(False,0)
+ box.pack_start(update_lesson, False, False, 0)
+ box = theme_box(box,BORDER_COLOR)
+
+ lesson_field = gtk.TextView()
+ lesson_field.modify_base(gtk.STATE_NORMAL, gtk.gdk.Color(BLUE))
+ lesson_field.set_wrap_mode(gtk.WRAP_WORD)
+ lesson_field.set_border_width(5)
+ lesson_buffer = lesson_field.get_buffer()
+ self.load_lessonplan(lesson_buffer)
+
+ lesson_field.modify_bg(gtk.STATE_NORMAL, gtk.gdk.Color(BUTTON_COLOR))
+ hbox.add(lesson_field)
+ hbox.set_border_width(5)
+ self.processtable.attach(hbox,0,2,0,11)
+ self.processtable.attach(box,0,2,11,12)
+ self.main_table.show_all()
+
+ def load_lessonplan(self, lesson_buffer):
+ fp = open(lessonplan_path, 'r')
+ lines = fp.readlines()
+
+ iter = lesson_buffer.get_iter_at_offset(0)
+
+ for line in lines:
+ lesson_buffer.insert(iter, line)
+
+ def rubrics_cb(self,widget,label):
+ self.processpanel.destroy()
+ self.build_processframe(label)
+
+ hpaned = gtk.HPaned()
+ hpaned.modify_bg(gtk.STATE_NORMAL, gtk.gdk.Color(BACKGROUND_COLOR))
+ hpaned.modify_bg(gtk.STATE_ACTIVE, gtk.gdk.Color(BACKGROUND_COLOR))
+ hpaned.set_border_width(5)
+ rubrics = RUBRICLIST
+ title = RUBRICTITLE
+ box1 = self.create_rubriclist(rubrics, title, "Predefined Rubrics")
+ rubrics = OTHER_RUBRICLIST
+ title = OTHER_RUBRICTITLE
+ box2 = self.create_rubriclist(rubrics, title, "Other Rubrics")
+
+ hpaned.set_position(450)
+ hpaned.add1(box1)
+ hpaned.add2(box2)
+
+ self.processtable.attach(hpaned,0,2,0,12)
+ self.main_table.show_all()
+
+ def create_rubriclist(self, rubrics, title, label):
+ rubricsbox = gtk.VBox(False,0)
+ buttons = []
+
+ separator0 = gtk.HSeparator()
+ rubricsbox.pack_start(separator0, False,False,2)
+ label = gtk.Label(label)
+ rubricsbox.pack_start(label, False,False,0)
+ separator1 = gtk.HSeparator()
+ rubricsbox.pack_start(separator1, False,False,2)
+
+ for i in range(len(title)):
+
+ for r in rubrics:
+ if str(title[i]) == str(r.title):
+ rubric = r
+ button = gtk.Button()
+ box = xpm_label_box( "images/green_button2.png", title[i])
+ button.add(box)
+ button = theme_button(button)
+ button.connect("clicked", self.viewrubric_cb, rubric)
+ buttons.append(button)
+ rubricsbox.pack_start(button,False, False,2)
+
+ rubricsbox = theme_box(rubricsbox, BACKGROUND_COLOR)
+ return rubricsbox
+
+ def viewrubric_cb(self, widget, rubric):
+ self.processpanel.destroy()
+ self.build_processframe("Rubric")
+
+ box = self.build_rubriclist(rubric)
+ sw = gtk.ScrolledWindow()
+ sw.set_policy(gtk.POLICY_AUTOMATIC,gtk.POLICY_AUTOMATIC)
+ sw.add_with_viewport(box)
+ sw.set_border_width(5)
+ sw = theme_box(sw, BUTTON_COLOR)
+ self.processtable.attach(sw,0,2,0,12)
+ self.main_table.show_all()
+
+
+ def build_rubriclist(self, rubric):
+ rubric_id = rubric.rubric_id
+ category = self.scorepadDB.queryall_category(rubric_id)
+ row = len(category)+1
+ category_id = category[0].category_id
+ column = len(self.scorepadDB.query_level(category_id))+1
+
+ column_names = self.scorepadDB.query_level(category_id)
+
+ levels = []
+
+ for i in range(column-1):
+ level = []
+ for j in range(row-1):
+ category_id = category[j].category_id
+ level_temp = self.scorepadDB.query_level(category_id)
+ level.append(level_temp[i].description)
+ levels.append(level)
+
+ view = self.build_categorylist(rubric, levels, column_names)
+ return view
+
+ def build_categorylist(self, rubric, levels,column_names):
+ tree_store = gtk.TreeStore( gobject.TYPE_STRING,gobject.TYPE_STRING,gobject.TYPE_STRING,
+ gobject.TYPE_STRING,gobject.TYPE_STRING)
+
+ column = [None, None, None, None]
+
+ for i in range(len(levels)):
+ column[i] = levels[i]
+
+ rubric_id = rubric.rubric_id
+ category = self.scorepadDB.queryall_category(rubric_id)
+
+ for i in range(len(category)):
+ tree_store.append( None, (category[i].name,
+ column[0][i], column[1][i],
+ column[2][i],column[3][i]) )
+
+ view = gtk.TreeView(tree_store)
+ view.modify_base(gtk.STATE_NORMAL, gtk.gdk.color_parse(BLUE))
+ view.set_rules_hint(True)
+ renderer = gtk.CellRendererText()
+ column0 = gtk.TreeViewColumn("Category", renderer, text=0)
+ column0.set_resizable(True)
+ view.append_column(column0)
+ for i in range(len(levels)):
+ render = gtk.CellRendererText()
+ column = gtk.TreeViewColumn(column_names[i].name, render, text=i+1)
+ column.set_resizable(True)
+ view.append_column(column)
+
+ return view
+
+ def submitgrade_cb(self, widget, tuple):
+ rubric = tuple[0]
+ project = tuple[1]
+
+ project_id = project.project_id
+ rubric_id = rubric.rubric_id
+ categories = self.scorepadDB.queryall_category(rubric_id)
+ self.initialize_score(project, rubric, categories)
+
+ category_id = categories[0].category_id
+ level_list = self.scorepadDB.query_level(category_id)
+ level_name = ""
+ for i in range(len(categories)):
+ category_id = categories[i].category_id
+ for j in range(len(level_list)+1):
+ if (self.tree_store[i][j] == True):
+ level_name = level_list[j-1].name
+ level_id = self.scorepadDB.querylevel_id(category_id, level_name)
+ score_id = self.scorepadDB.increment_scorecount(project_id, rubric_id, category_id, level_id)
+ score = self.scorepadDB.query_score2(project_id, rubric_id, category_id, level_id)
+ bundler = Bundler()
+ score_bundle = bundler.bundle_score(score)
+
+ is_send = False
+ if self.is_shared:
+ self.sendbundle_cb(score_bundle)
+ is_send = True
+ if is_send:
+ md = gtk.MessageDialog(parent = None, buttons = gtk.BUTTONS_OK, \
+ flags = gtk.DIALOG_DESTROY_WITH_PARENT, \
+ type = gtk.MESSAGE_INFO,\
+ message_format = "Evaluation sent!")
+ md.run()
+ md.destroy()
+
+ self.processpanel.destroy()
+ self.build_processframe("Projects")
+
+ tuplelist1 = (TITLELIST, PROJECTLIST)
+ tuplelist2 = (FRIENDSTITLELIST, FRIENDSPROJECTLIST)
+
+ panel = self.build_projectpanel(tuplelist1, tuplelist2)
+ panel.set_border_width(5)
+ self.processtable.attach(panel,0,2,0,11)
+
+ actionpanel = self.build_project_actionpanel()
+ self.processtable.attach(actionpanel,0,2,11,12)
+
+ self.main_table.show_all()
+
+ def initialize_score(self, project, rubric, categories):
+ project_id = project.project_id
+ rubric_id = rubric.rubric_id
+
+ for category in categories:
+ category_id = category.category_id
+ levels = self.scorepadDB.query_level(category_id)
+ for level in levels:
+ level_id = level.level_id
+ if self.scorepadDB.score_exists(project_id, rubric_id, category_id, level_id):
+ print 'score row exists'
+ else:
+ project_sha = self.scorepadDB.query_project_sha(project_id)
+ rubric_sha = self.scorepadDB.query_rubric_sha(rubric_id)
+ category_sha = self.scorepadDB.query_category_sha(category_id)
+ level_sha = self.scorepadDB.query_level_sha(level_id)
+ score = Score(None, project_id, rubric_id, category_id, level_id,
+ str(project_sha), str(rubric_sha),str(category_sha),str(level_sha), 0)
+ self.scorepadDB.insert_score(score)
+
+ def seegrades_cb(self, widget, data =None):
+ try:
+ if self.is_owned == True:
+ project = PROJECTLIST[self.selected_project]
+ rubric = self.scorepadDB.query_rubric(project.rubric_id)
+ self.processpanel.destroy()
+ self.build_processframe(project.title+" by "+project.author)
+
+ upperbox = self.build_upper_evalbox(project)
+ upperbox.modify_bg(gtk.STATE_NORMAL, gtk.gdk.Color(BACKGROUND_COLOR))
+
+ self.processtable.attach(upperbox,0,2,0,2)
+ self.build_lower_gradebox(project, rubric)
+ else:
+ md = gtk.MessageDialog(parent = None, buttons = gtk.BUTTONS_OK, \
+ flags = gtk.DIALOG_DESTROY_WITH_PARENT, \
+ type = gtk.MESSAGE_INFO,\
+ message_format = "Sorry. You cannot display your friend's grade")
+ md.run()
+ except:
+ md = gtk.MessageDialog(parent = None, buttons = gtk.BUTTONS_OK, \
+ flags = gtk.DIALOG_DESTROY_WITH_PARENT, \
+ type = gtk.MESSAGE_INFO,\
+ message_format = "Error. No project selected.")
+ md.run()
+ md.destroy()
+ md.destroy()
+
+ def build_lower_gradebox(self, project, rubric):
+ project_id = project.project_id
+ rubric_id = rubric.rubric_id
+ categories = self.scorepadDB.queryall_category(rubric_id)
+ category_id = categories[0].category_id
+
+ navpanel = gtk.HBox(False)
+ left_arrow = gtk.Button("Left")
+ left_arrow = theme_button(left_arrow)
+ right_arrow = gtk.Button("Right")
+ right_arrow = theme_button(right_arrow)
+ navpanel.add(left_arrow)
+ navpanel.add(right_arrow)
+ navpanel = theme_box(navpanel, BORDER_COLOR)
+ self.processtable.attach(navpanel,1,2,11,12)
+
+ category_list = ""
+ for category in categories:
+ category_list = category_list + str(category.name) +" "
+ level_list = ""
+ levels = self.scorepadDB.query_level(category_id)
+ for level in levels:
+ level_list = level_list + str(level.name) + " "
+
+ self.category_length = len(categories)
+ self.number_of_click = 3
+
+ if self.category_length == (1 or 2 or 3):
+ barchart = self.build_barchart(category_list, categories, level_list, project_id, rubric_id)
+ else :
+ partial_cat_list = ""
+ partial_cat_list = str(categories[0].name) + " " + str(categories[1].name) + " " + str(categories[2].name)
+ partial_cat = []
+ partial_cat.append(categories[0])
+ partial_cat.append(categories[1])
+ partial_cat.append(categories[2])
+ barchart = self.build_barchart(partial_cat_list, partial_cat, level_list, project_id, rubric_id)
+
+ self.chartbox = gtk.VBox()
+ self.chartbox.set_border_width(5)
+ self.chartbox.add(barchart)
+ self.chartbox = theme_box(self.chartbox, BUTTON_COLOR)
+ self.processtable.attach(self.chartbox, 0,2,2,11)
+ self.main_table.show_all()
+
+ navparam = [categories, level_list, project_id, rubric_id]
+ right_arrow.connect("clicked", self.navigate_cb, "0", navparam)
+ left_arrow.connect("clicked", self.navigate_cb, "1", navparam)
+
+ def navigate_cb(self, widget,position, navparam):
+ categories = navparam[0] #b
+ level_list = navparam[1] #c
+ project_id = navparam[2] #d
+ rubric_id = navparam[3] #e
+
+ self.chartbox.destroy()
+ self.chartbox = gtk.VBox()
+ self.chartbox.set_border_width(5)
+
+ if position == "0":
+ self.number_of_click = self.number_of_click + 3
+ else :
+ self.number_of_click = self.number_of_click -3
+
+ if self.number_of_click <= self.category_length and self.number_of_click != 0:
+ a = ""
+ a = str(categories[self.number_of_click-3].name) + " " + str(categories[self.number_of_click-2].name) + " " \
+ + str(categories[self.number_of_click-1].name)
+ b = []
+ b.append(categories[self.number_of_click-3])
+ b.append(categories[self.number_of_click-2])
+ b.append(categories[self.number_of_click-1])
+ barchart = self.build_barchart(a,b,level_list,project_id,rubric_id)
+ self.chartbox.add(barchart)
+ self.chartbox = theme_box(self.chartbox, BUTTON_COLOR)
+ self.processtable.attach(self.chartbox, 0,2,2,11)
+ self.main_table.show_all()
+ else:
+ if ((self.number_of_click - self.category_length) == 2):
+ a = ""
+ a = str(categories[self.category_length-1].name)
+ b = []
+ b.append(categories[self.category_length-1])
+ barchart = self.build_barchart(a,b,level_list,project_id,rubric_id)
+ self.chartbox.add(barchart)
+ self.chartbox = theme_box(self.chartbox, BUTTON_COLOR)
+ self.processtable.attach(self.chartbox, 0,2,2,11)
+ self.main_table.show_all()
+ if ((self.number_of_click - self.category_length) == 1):
+ a = ""
+ a = str(categories[self.category_length-2].name) + " " + str(categories[self.number_of_click-1].name)
+ b = []
+ b.append(categories[self.category_length-2])
+ b.append(categories[self.category_length-1])
+ barchart = self.build_barchart(a,b,level_list,project_id,rubric_id)
+ self.chartbox.add(barchart)
+ self.chartbox = theme_box(self.chartbox, BUTTON_COLOR)
+ self.processtable.attach(self.chartbox, 0,2,2,11)
+ self.main_table.show_all()
+ else:
+ print "Out of bounds"
+
+ def build_barchart(self,category_list, categories, level_list, project_id, rubric_id):
+ barchart = multi_bar_chart.MultiBarChart()
+ barchart.title.set_text('Grades')
+ barchart.set_mode(multi_bar_chart.MODE_HORIZONTAL)
+
+ i = 0
+ for categoryname in category_list.split():
+ categoryname_label = categoryname.capitalize()
+ multibar = multi_bar_chart.BarGroup(categoryname, categoryname_label)
+ category_id = categories[i].category_id
+ levels_temp = self.scorepadDB.query_level(category_id)
+ j = 0
+ for levelname in level_list.split():
+ count = 0
+ level_id = levels_temp[j].level_id
+ count = self.scorepadDB.query_score(project_id, rubric_id, category_id, level_id)
+ levelname_label = levelname.capitalize()
+ sub_bar = multi_bar_chart.Bar(levelname, count, levelname_label)
+ multibar.add_bar(sub_bar)
+ print "count : " + str(count)
+ j = j+1
+ barchart.add_bar(multibar)
+ i = i + 1
+ return barchart
+
+ def tutorial_cb(self, widget, data=None):
+ md = gtk.MessageDialog(parent = None, buttons = gtk.BUTTONS_OK, \
+ flags = gtk.DIALOG_DESTROY_WITH_PARENT, \
+ type = gtk.MESSAGE_INFO,\
+ message_format = "Under construction.")
+ md.run()
+ md.destroy()
+
+ def update_status(self, nick, text):
+ text = text.split("|")
+ model_name = text[0]
+
+ if model_name == "Project":
+ self._alert("Project was shared by", nick)
+ if self.is_exists:
+ rubric_id = self.rubric_exists
+ else:
+ rubric_id = self.scorepadDB.query_maxrubric()
+
+ project = Project(None, text[2], text[3], text[4], text[5],text[6],\
+ 0, 1, rubric_id, text[10], text[11])
+
+ if self.scorepadDB.project_exists(project.project_sha, project.author):
+ print "project exists"
+ else:
+ self.scorepadDB.insert_project(project)
+
+ project_id = self.scorepadDB.query_maxproject()
+ project = self.scorepadDB.query_project(project_id)
+
+ FRIENDSPROJECTLIST.append(project)
+ FRIENDSTITLELIST.append(project.title)
+ self.currently_shared = project.project_sha
+ tuplelist1 = (TITLELIST, PROJECTLIST)
+ tuplelist2 = (FRIENDSTITLELIST, FRIENDSPROJECTLIST)
+
+ self.processpanel.destroy()
+ self.build_processframe("Projects")
+ panel = self.build_projectpanel(tuplelist1, tuplelist2)
+ panel.modify_bg(gtk.STATE_NORMAL, gtk.gdk.Color(BACKGROUND_COLOR))
+ panel.set_border_width(5)
+ self.processtable.attach(panel,0,2,0,11)
+
+ actionpanel = self.build_project_actionpanel()
+ self.processtable.attach(actionpanel,0,2,11,12)
+
+ self.main_table.show_all()
+
+ if model_name == "Rubric":
+ rubric = Rubric(None, text[2], text[3], text[4], 0, text[6], text[7])
+
+ self.rubric_exists = self.scorepadDB.rubric_exists(rubric.rubric_sha, rubric.description)
+ if self.rubric_exists == None:
+ self.scorepadDB.insert_rubric(rubric)
+ rubric_id = self.scorepadDB.query_maxrubric()
+ rubric = self.scorepadDB.query_rubric(rubric_id)
+ OTHER_RUBRICLIST.append(rubric)
+ OTHER_RUBRICTITLE.append(rubric.title)
+ self.is_exists = False
+ else:
+ self.is_exists = True
+
+ if model_name == "Category":
+ if self.is_exists == False:
+ rubric_id = self.scorepadDB.query_maxrubric()
+ category = Category(None, text[2], rubric_id, text[4])
+ self.scorepadDB.insert_category(category)
+
+ if model_name == "Level":
+ if self.is_exists == False:
+ rubric_id = self.scorepadDB.query_maxrubric()
+ category_id = self.scorepadDB.query_maxcategory()
+ level = Level(None, text[2], text[3], category_id, rubric_id, text[6])
+ self.scorepadDB.insert_level(level)
+
+ if model_name == "Score":
+ self._alert("Score received from", nick)
+ project_sha = text[6]
+ rubric_sha = text[7]
+ category_sha = text[8]
+ level_sha = text[9]
+ score_id = self.scorepadDB.query_score_id(project_sha, rubric_sha, category_sha, level_sha)
+ attr = self.scorepadDB.query_score_attr(score_id)
+ self.scorepadDB.increment_scorecount(attr[0], attr[1], attr[2], attr[3])
+
+ def _alert(self, title, text=None):
+ alert = NotifyAlert(timeout=5)
+ alert.props.title = title
+ alert.props.msg = text
+ self.add_alert(alert)
+ alert.connect('response', self._alert_cancel_cb)
+ alert.show()
+
+ def _alert_cancel_cb(self, alert, response_id):
+ self.remove_alert(alert)
+
+ def _shared_cb(self, sender):
+ self._setup()
+ self.is_shared = True
+ self._alert('Shared', 'The activity is shared')
+
+ def _setup(self):
+ self.text_channel = TextChannelWrapper(
+ self.shared_activity.telepathy_text_chan,
+ self.shared_activity.telepathy_conn)
+ self.text_channel.set_received_callback(self._received_cb)
+ self._alert("Activity Shared", "Connected")
+ self.shared_activity.connect('buddy-joined', self._buddy_joined_cb)
+ self.shared_activity.connect('buddy-left', self._buddy_left_cb)
+
+ def _joined_cb(self, sender):
+ if not self.shared_activity:
+ return
+ for buddy in self.shared_activity.get_joined_buddies():
+ self._buddy_already_exists(buddy)
+ self.is_shared = True
+ self._setup()
+ self._alert("Joined", "Joined Scorepad Activity")
+
+ def _received_cb(self, buddy, text):
+ if buddy:
+ if type(buddy) is dict:
+ nick = buddy['nick']
+ else:
+ nick = buddy.props.nick
+ else:
+ nick = '???'
+ self.update_status(str(nick),text)
+
+ def _buddy_joined_cb(self, sender, buddy):
+ if buddy == self.owner:
+ return
+ self._alert(str(buddy.props.nick), "joined the activity")
+
+ def _buddy_left_cb(self, sender, buddy):
+ if buddy == self.owner:
+ return
+ self._alert(str(buddy.props.nick), "left")
+
+ def _buddy_already_exists(self, buddy):
+ if buddy == self.owner:
+ return
+ self._alert(str(buddy.props.nick), "is here")
+
+ def sendbundle_cb(self, bundle):
+ text = bundle
+ if text:
+ if self.text_channel:
+ self.text_channel.send(text)
+ else:
+ print "Not connected"
+ self._alert("Bundle", "sent!")
+
+class TextChannelWrapper(object):
+
+ def __init__(self, text_chan, conn):
+ self._activity_cb = None
+ self._text_chan = text_chan
+ self._conn = conn
+ self._signal_matches = []
+
+ def send(self, text):
+ if self._text_chan is not None:
+ self._text_chan[CHANNEL_TYPE_TEXT].Send(
+ CHANNEL_TEXT_MESSAGE_TYPE_NORMAL, text)
+
+ def set_received_callback(self, callback):
+ if self._text_chan is None:
+ return
+ self._activity_cb = callback
+ m = self._text_chan[CHANNEL_TYPE_TEXT].connect_to_signal('Received',
+ self._received_cb)
+ self._signal_matches.append(m)
+
+ def _received_cb(self, identity, timestamp, sender, type_, flags, text):
+ if self._activity_cb:
+ try:
+ self._text_chan[CHANNEL_INTERFACE_GROUP]
+ except Exception:
+ nick = self._conn[
+ CONN_INTERFACE_ALIASING].RequestAliases([sender])[0]
+ buddy = {'nick': nick, 'color': '#000000,#808080'}
+ else:
+ buddy = self._get_buddy(sender)
+ self._activity_cb(buddy, text)
+ self._text_chan[
+ CHANNEL_TYPE_TEXT].AcknowledgePendingMessages([identity])
+ else:
+ print "Disconnected"
+
+ def _get_buddy(self, cs_handle):
+ pservice = presenceservice.get_instance()
+ tp_name, tp_path = pservice.get_preferred_connection()
+ conn = Connection(tp_name, tp_path)
+ group = self._text_chan[CHANNEL_INTERFACE_GROUP]
+ my_csh = group.GetSelfHandle()
+ if my_csh == cs_handle:
+ handle = conn.GetSelfHandle()
+ elif group.GetGroupFlags() & \
+ CHANNEL_GROUP_FLAG_CHANNEL_SPECIFIC_HANDLES:
+ handle = group.GetHandleOwners([cs_handle])[0]
+ else:
+ handle = cs_handle
+ assert handle != 0
+
+ return pservice.get_buddy_by_telepathy_handle(
+ tp_name, tp_path, handle)
+
+ def destroy(self, widget, data=None):
+ gtk.main_quit()
+
+ def main(self):
+ gtk.main()
diff --git a/Template.py b/Template.py
new file mode 100644
index 0000000..c469c24
--- /dev/null
+++ b/Template.py
@@ -0,0 +1,97 @@
+from Models import *
+import datetime
+
+class Template():
+
+ def __init__(self, nickname = ""):
+ self.owner_nick = nickname
+ self.scorepadDB = ScorePadDB(self.owner_nick)
+
+ def save_template(self):
+
+ today = datetime.date.today()
+
+ rubric = Rubric(None, "Music", "Sample", "Music Rubric",1,self.owner_nick,"")
+ self.scorepadDB.insert_rubric(rubric)
+ rubric_id = self.scorepadDB.query_maxrubric()
+
+ category = Category(None, "Song", rubric_id,"")
+ level0 = Level(None, "Exemplary","Song relates to genre", None, rubric_id,"")
+ level1 = Level(None, "Proficient","Song loosely relates to genre", None, rubric_id,"")
+ level2 = Level(None, "Developing","Song doesn't relate to genre", None, rubric_id,"")
+ level3 = Level(None, "Unsatisfactory","No song chosen", None, rubric_id,"")
+
+ levels = [level0, level1, level2, level3]
+ self.scorepadDB.insert_criteria(category, levels)
+
+ category = Category(None, "Presentation", rubric_id,"")
+ level0 = Level(None, "Exemplary","Song is explained.Tied well to genre.", None, rubric_id,"")
+ level1 = Level(None, "Proficient","Song explained but not tied well to \
+ genre/assignment.", None, rubric_id,"")
+ level2 = Level(None, "Developing","Vague explanation given.", None, rubric_id,"")
+ level3 = Level(None, "Unsatisfactory","No historical context given.", None, rubric_id,"")
+ levels = []
+ levels = [level0, level1, level2, level3]
+ self.scorepadDB.insert_criteria(category, levels)
+
+ category = Category(None, "Performance", rubric_id,"")
+ level0 = Level(None, "Exemplary","Taken seriously. Performed well", None, rubric_id,"")
+ level1 = Level(None, "Proficient","Performed well with some mistakes", None, rubric_id,"")
+ level2 = Level(None, "Developing","Not taken very seriously", None, rubric_id,"")
+ level3 = Level(None, "Unsatisfactory","Weak performance", None, rubric_id,"")
+ levels = []
+ levels = [level0, level1, level2, level3]
+ self.scorepadDB.insert_criteria(category, levels)
+
+ category = Category(None, "Effort", rubric_id,"")
+ level0 = Level(None, "Exemplary","Went above and beyond for effort", None, rubric_id,"")
+ level1 = Level(None, "Proficient","Put significant effort", None, rubric_id,"")
+ level2 = Level(None, "Developing","Effort put is only asked in class", None, rubric_id,"")
+ level3 = Level(None, "Unsatisfactory","No effort put in", None, rubric_id,"")
+ levels = []
+ levels = [level0, level1, level2, level3]
+ self.scorepadDB.insert_criteria(category, levels)
+
+ project = Project(None, "My song", "Sample", "Great Song","Music I",\
+ str(today), 1, 1,rubric_id,self.owner_nick,"")
+
+ print project.subject
+ self.scorepadDB.insert_project(project)
+
+
+ rubric = Rubric(None, "Art", "Sample", "Art Rubric",1,self.owner_nick,"")
+ self.scorepadDB.insert_rubric(rubric)
+ rubric_id = self.scorepadDB.query_maxrubric()
+
+ category = Category(None, "Creativity", rubric_id,"")
+ level0 = Level(None, "Exemplary","Generating many ideas", None, rubric_id,"")
+ level1 = Level(None, "Proficient","Based his or her work on someone else's idea", None, rubric_id,"")
+ level2 = Level(None, "Developing","Lacked originality", None, rubric_id,"")
+ level3 = Level(None, "Unsatisfactory","No evidence of trying anything unusual", None, rubric_id,"")
+ levels = []
+ levels = [level0, level1, level2, level3]
+ self.scorepadDB.insert_criteria(category, levels)
+
+ category = Category(None, "Effort", rubric_id,"")
+ level0 = Level(None, "Exemplary","Gave it effort far beyond that required", None, rubric_id,"")
+ level1 = Level(None, "Proficient","The student work hard and completed the project", None, rubric_id,"")
+ level2 = Level(None, "Developing","Chose an easy project and did it indifferently", None, rubric_id,"")
+ level3 = Level(None, "Unsatisfactory","Completed with minimum effort", None, rubric_id,"")
+ levels = []
+ levels = [level0, level1, level2, level3]
+ self.scorepadDB.insert_criteria(category, levels)
+
+ category = Category(None, "Craftsmanship/Skill", rubric_id,"")
+ level0 = Level(None, "Exemplary","The artwork was beautiful and patiently done", None, rubric_id,"")
+ level1 = Level(None, "Proficient","Lacks the finishing touches", None, rubric_id,"")
+ level2 = Level(None, "Developing","The student showed average craftsmanship", None, rubric_id,"")
+ level3 = Level(None, "Unsatisfactory","The student showed below average craftsmanship", None, rubric_id,"")
+ levels = []
+ levels = [level0, level1, level2, level3]
+ self.scorepadDB.insert_criteria(category, levels)
+
+ project2 = Project(None, "My Mosaic", "Sample", "Art Project","Art Stud I",\
+ str(today), 0, 1,rubric_id,self.owner_nick,"")
+
+ self.scorepadDB.insert_project(project2)
+
diff --git a/activity/activity-scorepad.svg b/activity/activity-scorepad.svg
new file mode 100644
index 0000000..6d62ab6
--- /dev/null
+++ b/activity/activity-scorepad.svg
@@ -0,0 +1,24 @@
+<?xml version="1.0" ?><!-- Created with Inkscape (http://www.inkscape.org/) --><!DOCTYPE svg PUBLIC '-//W3C//DTD SVG 1.1//EN' 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd' [
+ <!ENTITY stroke_color "#666666">
+ <!ENTITY fill_color "#ffffff">
+]><svg height="48px" id="svg2985" inkscape:version="0.48.0 r9654" sodipodi:docname="New document 2" version="1.1" width="48px" xmlns="http://www.w3.org/2000/svg" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:svg="http://www.w3.org/2000/svg">
+ <defs id="defs2987"/>
+ <sodipodi:namedview bordercolor="#666666" borderopacity="1.0" id="base" inkscape:current-layer="layer1" inkscape:cx="24" inkscape:cy="24" inkscape:document-units="px" inkscape:grid-bbox="true" inkscape:pageopacity="0.0" inkscape:pageshadow="2" inkscape:window-height="626" inkscape:window-maximized="0" inkscape:window-width="901" inkscape:window-x="233" inkscape:window-y="78" inkscape:zoom="7" pagecolor="#ffffff" showgrid="true"/>
+ <metadata id="metadata2990">
+ <rdf:RDF>
+ <cc:Work rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/>
+ <dc:title/>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g id="layer1" inkscape:groupmode="layer" inkscape:label="Layer 1">
+ <path d="M 5,9.9999996 9.1428571,16.857143 23.142857,3 9.7142857,12.714286 z" id="path2995" inkscape:connector-curvature="0" style="fill:none;stroke:&stroke_color;;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"/>
+ <path d="M 5.6428568,22.785714 9.7857138,29.642858 23.785714,15.785715 10.357143,25.500001 z" id="path2995-1" inkscape:connector-curvature="0" style="fill:none;stroke:&stroke_color;;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"/>
+ <path d="M 6.3571434,36.071428 10.5,42.928571 24.499999,29.071428 11.071428,38.785714 z" id="path2995-8" inkscape:connector-curvature="0" style="fill:none;stroke:&stroke_color;;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"/>
+ <rect height="2" id="rect3810" style="fill:&fill_color;;fill-rule:evenodd;stroke:&stroke_color;;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" width="22.142857" x="23.142857" y="8.5714283"/>
+ <rect height="2" id="rect3810-1" style="fill:&fill_color;;fill-rule:evenodd;stroke:&stroke_color;;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" width="22.142857" x="23.214287" y="22.428572"/>
+ <rect height="2" id="rect3810-8" style="fill:&fill_color;;fill-rule:evenodd;stroke:&stroke_color;;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" width="22.142857" x="23.357145" y="35.714287"/>
+ </g>
+</svg> \ No newline at end of file
diff --git a/activity/activity.info b/activity/activity.info
new file mode 100644
index 0000000..93b8dcc
--- /dev/null
+++ b/activity/activity.info
@@ -0,0 +1,8 @@
+[Activity]
+name = ScorePad
+bundle_id = org.laptop.ScorePadActivity
+class = ScorePadActivity.ScorePadActivity
+icon = activity-scorepad
+activity_version = 1
+host_version = 1
+show_launcher = yes
diff --git a/db/cnixdb.sqlite b/db/cnixdb.sqlite
new file mode 100644
index 0000000..e2ff351
--- /dev/null
+++ b/db/cnixdb.sqlite
Binary files differ
diff --git a/dist/ScorePad-1.xo b/dist/ScorePad-1.xo
new file mode 100644
index 0000000..3272bd4
--- /dev/null
+++ b/dist/ScorePad-1.xo
Binary files differ
diff --git a/images/background.jpg b/images/background.jpg
new file mode 100644
index 0000000..7626c7d
--- /dev/null
+++ b/images/background.jpg
Binary files differ
diff --git a/images/green_button2.png b/images/green_button2.png
new file mode 100644
index 0000000..2b80abd
--- /dev/null
+++ b/images/green_button2.png
Binary files differ
diff --git a/images/hello2.png b/images/hello2.png
new file mode 100644
index 0000000..cb6b18c
--- /dev/null
+++ b/images/hello2.png
Binary files differ
diff --git a/images/logo4.png b/images/logo4.png
new file mode 100644
index 0000000..9e9905d
--- /dev/null
+++ b/images/logo4.png
Binary files differ
diff --git a/images/logo5.png b/images/logo5.png
new file mode 100644
index 0000000..cde25be
--- /dev/null
+++ b/images/logo5.png
Binary files differ
diff --git a/images/start.png b/images/start.png
new file mode 100644
index 0000000..1410bca
--- /dev/null
+++ b/images/start.png
Binary files differ
diff --git a/images/wel2.png b/images/wel2.png
new file mode 100644
index 0000000..b5a41b6
--- /dev/null
+++ b/images/wel2.png
Binary files differ
diff --git a/pygtk_chart/__init__.py b/pygtk_chart/__init__.py
new file mode 100644
index 0000000..891e603
--- /dev/null
+++ b/pygtk_chart/__init__.py
@@ -0,0 +1,53 @@
+#!/usr/bin/env python
+#
+# __init__.py
+#
+# Copyright 2008 Sven Festersen <sven@sven-festersen.de>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+# MA 02110-1301, USA.
+"""
+This package contains four pygtk widgets for drawing simple charts:
+ - line_chart.LineChart for line charts,
+ - pie_chart.PieChart for pie charts,
+ - bar_chart.BarChart for bar charts,
+ - bar_chart.MultiBarChart for charts with groups of bars.
+"""
+__docformat__ = "epytext"
+
+__version__ = "beta"
+__author__ = "Sven Festersen, John Dickinson"
+__license__ = "GPL"
+__url__ = "http://notmyname.github.com/pygtkChart/"
+
+import os
+from pygtk_chart.basics import gdk_color_list_from_file
+COLOR_AUTO = 0
+COLORS = gdk_color_list_from_file(os.sep.join([os.path.dirname(__file__), "data", "tango.color"]))
+
+#line style
+LINE_STYLE_SOLID = 0
+LINE_STYLE_DOTTED = 1
+LINE_STYLE_DASHED = 2
+LINE_STYLE_DASHED_ASYMMETRIC = 3
+
+#point styles
+POINT_STYLE_CIRCLE = 0
+POINT_STYLE_SQUARE = 1
+POINT_STYLE_CROSS = 2
+POINT_STYLE_TRIANGLE_UP = 3
+POINT_STYLE_TRIANGLE_DOWN = 4
+POINT_STYLE_DIAMOND = 5
+
diff --git a/pygtk_chart/__init__.pyc b/pygtk_chart/__init__.pyc
new file mode 100644
index 0000000..a4ba455
--- /dev/null
+++ b/pygtk_chart/__init__.pyc
Binary files differ
diff --git a/pygtk_chart/bar_chart.py b/pygtk_chart/bar_chart.py
new file mode 100644
index 0000000..d1e8879
--- /dev/null
+++ b/pygtk_chart/bar_chart.py
@@ -0,0 +1,729 @@
+# Copyright 2009 John Dickinson <john@johnandkaren.com>
+# Sven Festersen <sven@sven-festersen.de>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+# MA 02110-1301, USA.
+"""
+Contains the BarChart widget.
+
+Author: John Dickinson (john@johnandkaren.com),
+Sven Festersen (sven@sven-festersen.de)
+"""
+__docformat__ = "epytext"
+import cairo
+import gtk
+import gobject
+import os
+import math
+
+import pygtk_chart
+from pygtk_chart.basics import *
+from pygtk_chart.chart_object import ChartObject
+from pygtk_chart import chart
+from pygtk_chart import label
+
+from pygtk_chart import COLORS, COLOR_AUTO
+
+MODE_VERTICAL = 0
+MODE_HORIZONTAL = 1
+
+def draw_rounded_rectangle(context, x, y, width, height, radius=0):
+ """
+ Draws a rectangle with rounded corners to context. radius specifies
+ the corner radius in px.
+
+ @param context: the context to draw on
+ @type context: CairoContext
+ @param x: x coordinate of the upper left corner
+ @type x: float
+ @param y: y coordinate of the upper left corner
+ @type y: float
+ @param width: width of the rectangle in px
+ @type width: float
+ @param height: height of the rectangle in px
+ @type height: float
+ @param radius: corner radius in px (default: 0)
+ @type radius: float.
+ """
+ if radius == 0:
+ context.rectangle(x, y, width, height)
+ else:
+ context.move_to(x, y + radius)
+ context.arc(x + radius, y + radius, radius, math.pi, 1.5 * math.pi)
+ context.rel_line_to(width - 2 * radius, 0)
+ context.arc(x + width - radius, y + radius, radius, 1.5 * math.pi, 2 * math.pi)
+ context.rel_line_to(0, height - 2 * radius)
+ context.arc(x + width - radius, y + height - radius, radius, 0, 0.5 * math.pi)
+ context.rel_line_to(-(width - 2 * radius), 0)
+ context.arc(x + radius, y + height - radius, radius, 0.5 * math.pi, math.pi)
+ context.close_path()
+
+
+class Bar(chart.Area):
+ """
+ A class that represents a bar on a bar chart.
+
+ Properties
+ ==========
+ The Bar class inherits properties from chart.Area.
+ Additional properties:
+ - corner-radius (radius of the bar's corners, in px; type: float)
+
+ Signals
+ =======
+ The Bar class inherits signals from chart.Area.
+ """
+
+ __gproperties__ = {"corner-radius": (gobject.TYPE_INT, "bar corner radius",
+ "The radius of the bar's rounded corner.",
+ 0, 100, 0, gobject.PARAM_READWRITE)}
+
+ def __init__(self, name, value, title=""):
+ chart.Area.__init__(self, name, value, title)
+ self._label_object = label.Label((0, 0), title)
+ self._value_label_object = label.Label((0, 0), "")
+
+ self._corner_radius = 0
+
+ def do_get_property(self, property):
+ if property.name == "visible":
+ return self._show
+ elif property.name == "antialias":
+ return self._antialias
+ elif property.name == "name":
+ return self._name
+ elif property.name == "value":
+ return self._value
+ elif property.name == "color":
+ return self._color
+ elif property.name == "label":
+ return self._label
+ elif property.name == "highlighted":
+ return self._highlighted
+ elif property.name == "corner-radius":
+ return self._corner_radius
+ else:
+ raise AttributeError, "Property %s does not exist." % property.name
+
+ def do_set_property(self, property, value):
+ if property.name == "visible":
+ self._show = value
+ elif property.name == "antialias":
+ self._antialias = value
+ elif property.name == "value":
+ self._value = value
+ elif property.name == "color":
+ self._color = value
+ elif property.name == "label":
+ self._label = value
+ elif property.name == "highlighted":
+ self._highlighted = value
+ elif property.name == "corner-radius":
+ self._corner_radius = value
+ else:
+ raise AttributeError, "Property %s does not exist." % property.name
+
+ def _do_draw(self, context, rect, n, i, mode, max_value, bar_padding, value_label_size, label_size, draw_labels):
+ if mode == MODE_VERTICAL:
+ self._do_draw_single_vertical(context, rect, n, i, mode, max_value, bar_padding, value_label_size, label_size, draw_labels)
+ elif mode == MODE_HORIZONTAL:
+ self._do_draw_single_horizontal(context, rect, n, i, mode, max_value, bar_padding, value_label_size, label_size, draw_labels)
+
+ def _do_draw_single_vertical(self, context, rect, n, i, mode, max_value, bar_padding, value_label_size, label_size, draw_labels):
+ bar_width = (rect.width - (n - 1) * bar_padding) / n
+ bar_height = (rect.height - value_label_size - label_size) * self._value / max_value
+ bar_x = rect.x + i * (bar_width + bar_padding)
+ bar_y = rect.y + rect.height - bar_height - label_size
+ context.set_source_rgb(*color_gdk_to_cairo(self._color))
+ draw_rounded_rectangle(context, bar_x, bar_y, bar_width, bar_height, self._corner_radius)
+ context.fill()
+
+ if self._highlighted:
+ context.set_source_rgba(1, 1, 1, 0.1)
+ draw_rounded_rectangle(context, bar_x, bar_y, bar_width, bar_height, self._corner_radius)
+ context.fill()
+
+ if draw_labels:
+ #draw the value label
+ self._value_label_object.set_text(str(self._value))
+ self._value_label_object.set_color(self._color)
+ self._value_label_object.set_max_width(bar_width)
+ self._value_label_object.set_position((bar_x + bar_width / 2, bar_y - 3))
+ self._value_label_object.set_anchor(label.ANCHOR_BOTTOM_CENTER)
+ self._value_label_object.draw(context, rect)
+ context.fill()
+
+ #draw label
+ self._label_object.set_text(self._label)
+ self._label_object.set_color(self._color)
+ self._label_object.set_max_width(bar_width)
+ self._label_object.set_position((bar_x + bar_width / 2, bar_y + bar_height + 3))
+ self._label_object.set_anchor(label.ANCHOR_TOP_CENTER)
+ self._label_object.draw(context, rect)
+ context.fill()
+
+ chart.add_sensitive_area(chart.AREA_RECTANGLE, (bar_x, bar_y, bar_width, bar_height), self)
+
+ def _do_draw_single_horizontal(self, context, rect, n, i, mode, max_value, bar_padding, value_label_size, label_size, draw_labels):
+ bar_width = (rect.width - value_label_size - label_size) * self._value / max_value
+ bar_height = (rect.height - (n - 1) * bar_padding) / n
+ bar_x = rect.x + label_size
+ bar_y = rect.y + i * (bar_height + bar_padding)
+ context.set_source_rgb(*color_gdk_to_cairo(self._color))
+ draw_rounded_rectangle(context, bar_x, bar_y, bar_width, bar_height, self._corner_radius)
+ context.fill()
+
+ if self._highlighted:
+ context.set_source_rgba(1, 1, 1, 0.1)
+ draw_rounded_rectangle(context, bar_x, bar_y, bar_width, bar_height, self._corner_radius)
+ context.fill()
+
+ if draw_labels:
+ #draw the value label
+ self._value_label_object.set_text(str(self._value))
+ self._value_label_object.set_color(self._color)
+ self._value_label_object.set_position((bar_x + bar_width + 3, bar_y + bar_height / 2))
+ self._value_label_object.set_anchor(label.ANCHOR_LEFT_CENTER)
+ self._value_label_object.draw(context, rect)
+ context.fill()
+
+ #draw label
+ self._label_object.set_text(self._label)
+ self._label_object.set_color(self._color)
+ self._label_object.set_max_width(0.25 * rect.width)
+ self._label_object.set_position((bar_x - 3, bar_y + bar_height / 2))
+ self._label_object.set_anchor(label.ANCHOR_RIGHT_CENTER)
+ self._label_object.draw(context, rect)
+ context.fill()
+
+ chart.add_sensitive_area(chart.AREA_RECTANGLE, (bar_x, bar_y, bar_width, bar_height), self)
+
+ def get_value_label_size(self, context, rect, mode, n, bar_padding):
+ if mode == MODE_VERTICAL:
+ bar_width = (rect.width - (n - 1) * bar_padding) / n
+ self._value_label_object.set_max_width(bar_width)
+ self._value_label_object.set_text(str(self._value))
+ return self._value_label_object.get_calculated_dimensions(context, rect)[1]
+ elif mode == MODE_HORIZONTAL:
+ self._value_label_object.set_wrap(False)
+ self._value_label_object.set_fixed(True)
+ self._value_label_object.set_text(str(self._value))
+ return self._value_label_object.get_calculated_dimensions(context, rect)[0]
+
+ def get_label_size(self, context, rect, mode, n, bar_padding):
+ if mode == MODE_VERTICAL:
+ bar_width = (rect.width - (n - 1) * bar_padding) / n
+ self._label_object.set_max_width(bar_width)
+ self._label_object.set_text(self._label)
+ return self._label_object.get_calculated_dimensions(context, rect)[1]
+ elif mode == MODE_HORIZONTAL:
+ self._label_object.set_max_width(0.25 * rect.width)
+ self._label_object.set_text(self._label)
+ return self._label_object.get_calculated_dimensions(context, rect)[0]
+
+ def set_corner_radius(self, radius):
+ """
+ Set the radius of the bar's corners in px (default: 0).
+
+ @param radius: radius of the corners
+ @type radius: int in [0, 100].
+ """
+ self.set_property("corner-radius", radius)
+ self.emit("appearance_changed")
+
+ def get_corner_radius(self):
+ """
+ Returns the current radius of the bar's corners in px.
+
+ @return: int in [0, 100]
+ """
+ return self.get_property("corner-radius")
+
+
+class Grid(ChartObject):
+ """
+ This class represents the grid on BarChart and MultiBarChart
+ widgets.
+
+ Properties
+ ==========
+ bar_chart.Grid inherits properties from ChartObject.
+ Additional properties:
+ - line-style (the style of the grid lines, type: a line style
+ constant)
+ - color (the color of the grid lines, type: gtk.gdk.Color)
+ - show-values (sets whether values should be shown at the grid
+ lines, type: boolean)
+ - padding (the grid's padding in px, type: int in [0, 100]).
+
+ Signals
+ =======
+ The Grid class inherits signal from chart_object.ChartObject.
+ """
+
+ __gproperties__ = {"show-values": (gobject.TYPE_BOOLEAN, "show values",
+ "Set whether to show grid values.",
+ True, gobject.PARAM_READWRITE),
+ "color": (gobject.TYPE_PYOBJECT, "color",
+ "The color of the grid lines.",
+ gobject.PARAM_READWRITE),
+ "line-style": (gobject.TYPE_INT, "line style",
+ "The grid's line style", 0, 3, 0,
+ gobject.PARAM_READWRITE),
+ "padding": (gobject.TYPE_INT, "padding",
+ "The grid's padding", 0, 100, 6,
+ gobject.PARAM_READWRITE)}
+
+ def __init__(self):
+ ChartObject.__init__(self)
+ #private properties:
+ self._show_values = True
+ self._color = gtk.gdk.color_parse("#dedede")
+ self._line_style = pygtk_chart.LINE_STYLE_SOLID
+ self._padding = 6
+
+ def do_get_property(self, property):
+ if property.name == "visible":
+ return self._show
+ elif property.name == "antialias":
+ return self._antialias
+ elif property.name == "show-values":
+ return self._show_values
+ elif property.name == "color":
+ return self._color
+ elif property.name == "line-style":
+ return self._line_style
+ elif property.name == "padding":
+ return self._padding
+ else:
+ raise AttributeError, "Property %s does not exist." % property.name
+
+ def do_set_property(self, property, value):
+ if property.name == "visible":
+ self._show = value
+ elif property.name == "antialias":
+ self._antialias = value
+ elif property.name == "show-values":
+ self._show_values = value
+ elif property.name == "color":
+ self._color = value
+ elif property.name == "line-style":
+ self._line_style = value
+ elif property.name == "padding":
+ self._padding = value
+ else:
+ raise AttributeError, "Property %s does not exist." % property.name
+
+ def _do_draw(self, context, rect, mode, maximum_value, value_label_size, label_size):
+ n = maximum_value / (10 ** int(math.log10(maximum_value)))
+ context.set_antialias(cairo.ANTIALIAS_NONE)
+ set_context_line_style(context, self._line_style)
+ labels = []
+ if mode == MODE_VERTICAL:
+ delta = (rect.height - value_label_size - label_size) / n
+ if self._show_values:
+ max_label_size = 0
+ for i in range(0, int(n + 1)):
+ y = rect.y + rect.height - i * delta - label_size
+ value = maximum_value * float(i) / n
+ value_label = label.Label((rect.x, y), str(value))
+ max_label_size = max(max_label_size, value_label.get_calculated_dimensions(context, rect)[0])
+ labels.append(value_label)
+ max_label_size += 3
+ rect = gtk.gdk.Rectangle(int(rect.x + max_label_size), rect.y, int(rect.width - max_label_size), rect.height)
+ for i in range(0, len(labels)):
+ y = rect.y + rect.height - i * delta - label_size
+ value_label = labels[i]
+ value_label.set_position((rect.x - 3, y))
+ value_label.set_anchor(label.ANCHOR_RIGHT_CENTER)
+ value_label.draw(context, rect)
+ context.fill()
+
+ for i in range(0, int(n + 1)):
+ y = rect.y + rect.height - i * delta - label_size
+ context.set_source_rgb(*color_gdk_to_cairo(self._color))
+ context.move_to(rect.x, y)
+ context.rel_line_to(rect.width, 0)
+ context.stroke()
+ rect = gtk.gdk.Rectangle(rect.x + self._padding, rect.y, rect.width - 2 * self._padding, rect.height)
+ elif mode == MODE_HORIZONTAL:
+ delta = (rect.width - value_label_size - label_size) / n
+
+ if self._show_values:
+ max_label_size = 0
+ for i in range(0, int(n + 1)):
+ x = rect.x + i * delta + label_size
+ value = maximum_value * float(i) / n
+ value_label = label.Label((x, rect.y + rect.height), str(value))
+ max_label_size = max(max_label_size, value_label.get_calculated_dimensions(context, rect)[1])
+ labels.append(value_label)
+ max_label_size += 3
+ rect = gtk.gdk.Rectangle(rect.x, rect.y, rect.width, int(rect.height - max_label_size))
+ for i in range(0, len(labels)):
+ x = rect.x + i * delta + label_size
+ value_label = labels[i]
+ value_label.set_position((x, rect.y + rect.height + 3))
+ value_label.set_anchor(label.ANCHOR_TOP_CENTER)
+ value_label.draw(context, rect)
+ context.fill()
+
+ for i in range(0, int(n + 1)):
+ x = rect.x + i * delta + label_size
+ context.set_source_rgb(*color_gdk_to_cairo(self._color))
+ context.move_to(x, rect.y)
+ context.rel_line_to(0, rect.height)
+ context.stroke()
+ rect = gtk.gdk.Rectangle(rect.x, rect.y + self._padding, rect.width, rect.height - 2 * self._padding)
+ return rect
+
+ #set and get methods
+ def set_show_values(self, show):
+ """
+ Set whether values should be shown.
+
+ @type show: boolean.
+ """
+ self.set_property("show-values", show)
+ self.emit("appearance_changed")
+
+ def get_show_values(self):
+ """
+ Returns True if grid values are shown.
+
+ @return: boolean.
+ """
+ return self.get_property("show-values")
+
+ def set_color(self, color):
+ """
+ Set the color of the grid lines.
+
+ @param color: the grid lines' color
+ @type color: gtk.gdk.Color.
+ """
+ self.set_property("color", color)
+ self.emit("appearance_changed")
+
+ def get_color(self):
+ """
+ Returns the current color of the grid lines.
+
+ @return: gtk.gdk.Color.
+ """
+ return self.get_property("color")
+
+ def set_line_style(self, style):
+ """
+ Set the style of the grid lines. style has to be one of
+ - pygtk_chart.LINE_STYLE_SOLID (default)
+ - pygtk_chart.LINE_STYLE_DOTTED
+ - pygtk_chart.LINE_STYLE_DASHED
+ - pygtk_chart.LINE_STYLE_DASHED_ASYMMETRIC
+
+ @param style: the new line style
+ @type style: one of the constants above.
+ """
+ self.set_property("line-style", style)
+ self.emit("appearance_changed")
+
+ def get_line_style(self):
+ """
+ Returns the current grid's line style.
+
+ @return: a line style constant.
+ """
+ return self.get_property("line-style")
+
+ def set_padding(self, padding):
+ """
+ Set the grid's padding.
+
+ @type padding: int in [0, 100].
+ """
+ self.set_property("padding", padding)
+ self.emit("appearance_changed")
+
+ def get_padding(self):
+ """
+ Returns the grid's padding.
+
+ @return: int in [0, 100].
+ """
+ return self.get_property("padding")
+
+
+
+class BarChart(chart.Chart):
+ """
+ This is a widget that show a simple BarChart.
+
+ Properties
+ ==========
+ The BarChart class inherits properties from chart.Chart.
+ Additional properites:
+ - draw-labels (set wether to draw bar label, type: boolean)
+ - enable-mouseover (set whether to show a mouseover effect, type:
+ boolean)
+ - mode (the mode of the bar chart, type: one of MODE_VERTICAL,
+ MODE_HORIZONTAL)
+ - bar-padding (the sace between bars in px, type: int in [0, 100]).
+
+ Signals
+ =======
+ The BarChart class inherits signals from chart.Chart.
+ Additional signals:
+ - bar-clicked: emitted when a bar on the bar chart was clicked
+ callback signature:
+ def bar_clicked(chart, bar).
+
+ """
+
+ __gsignals__ = {"bar-clicked": (gobject.SIGNAL_RUN_LAST,
+ gobject.TYPE_NONE,
+ (gobject.TYPE_PYOBJECT,))}
+
+ __gproperties__ = {"bar-padding": (gobject.TYPE_INT, "bar padding",
+ "The distance between two bars.",
+ 0, 100, 16,
+ gobject.PARAM_READWRITE),
+ "mode": (gobject.TYPE_INT, "mode",
+ "The chart's mode.", 0, 1, 0,
+ gobject.PARAM_READWRITE),
+ "draw-labels": (gobject.TYPE_BOOLEAN,
+ "draw labels", "Set whether to draw labels on bars.",
+ True, gobject.PARAM_READWRITE),
+ "enable-mouseover": (gobject.TYPE_BOOLEAN, "enable mouseover",
+ "Set whether to enable mouseover effect.",
+ True, gobject.PARAM_READWRITE)}
+
+ def __init__(self):
+ super(BarChart, self).__init__()
+ #private properties:
+ self._bars = []
+ #gobject properties:
+ self._bar_padding = 16
+ self._mode = MODE_VERTICAL
+ self._draw_labels = True
+ self._mouseover = True
+ #public attributes:
+ self.grid = Grid()
+ #connect callbacks:
+ self.grid.connect("appearance_changed", self._cb_appearance_changed)
+
+ def do_get_property(self, property):
+ if property.name == "padding":
+ return self._padding
+ elif property.name == "bar-padding":
+ return self._bar_padding
+ elif property.name == "mode":
+ return self._mode
+ elif property.name == "draw-labels":
+ return self._draw_labels
+ elif property.name == "enable-mouseover":
+ return self._mouseover
+ else:
+ raise AttributeError, "Property %s does not exist." % property.name
+
+ def do_set_property(self, property, value):
+ if property.name == "padding":
+ self._padding = value
+ elif property.name == "bar-padding":
+ self._bar_padding = value
+ elif property.name == "mode":
+ self._mode = value
+ elif property.name == "draw-labels":
+ self._draw_labels = value
+ elif property.name == "enable-mouseover":
+ self._mouseover = value
+ else:
+ raise AttributeError, "Property %s does not exist." % property.name
+
+ #drawing methods
+ def draw(self, context):
+ """
+ Draw the widget. This method is called automatically. Don't call it
+ yourself. If you want to force a redrawing of the widget, call
+ the queue_draw() method.
+
+ @type context: cairo.Context
+ @param context: The context to draw on.
+ """
+ label.begin_drawing()
+
+ rect = self.get_allocation()
+ rect = gtk.gdk.Rectangle(0, 0, rect.width, rect.height) #transform rect to context coordinates
+ context.set_line_width(1)
+
+ rect = self.draw_basics(context, rect)
+ maximum_value = max(bar.get_value() for bar in self._bars)
+ #find out the size of the value labels
+ value_label_size = 0
+ if self._draw_labels:
+ for bar in self._bars:
+ value_label_size = max(value_label_size, bar.get_value_label_size(context, rect, self._mode, len(self._bars), self._bar_padding))
+ value_label_size += 3
+
+ #find out the size of the labels:
+ label_size = 0
+ if self._draw_labels:
+ for bar in self._bars:
+ label_size = max(label_size, bar.get_label_size(context, rect, self._mode, len(self._bars), self._bar_padding))
+ label_size += 3
+
+ rect = self._do_draw_grid(context, rect, maximum_value, value_label_size, label_size)
+ self._do_draw_bars(context, rect, maximum_value, value_label_size, label_size)
+
+ label.finish_drawing()
+
+ if self._mode == MODE_VERTICAL:
+ n = len(self._bars)
+ minimum_width = rect.x + self._padding + (n - 1) * self._bar_padding + n * 10
+ minimum_height = 100 + self._padding + rect.y
+ elif self._mode == MODE_HORIZONTAL:
+ n = len(self._bars)
+ minimum_width = rect.x + self._bar_padding + 100
+ minimum_height = rect.y + self._padding + (n - 1) * self._bar_padding + n * 10
+ self.set_size_request(minimum_width, minimum_height)
+
+ def draw_basics(self, context, rect):
+ """
+ Draw basic things that every plot has (background, title, ...).
+
+ @type context: cairo.Context
+ @param context: The context to draw on.
+ @type rect: gtk.gdk.Rectangle
+ @param rect: A rectangle representing the charts area.
+ """
+ self.background.draw(context, rect)
+ self.title.draw(context, rect, self._padding)
+
+ #calculate the rectangle that's available for drawing the chart
+ title_height = self.title.get_real_dimensions()[1]
+ rect_height = int(rect.height - 3 * self._padding - title_height)
+ rect_width = int(rect.width - 2 * self._padding)
+ rect_x = int(rect.x + self._padding)
+ rect_y = int(rect.y + title_height + 2 * self._padding)
+ return gtk.gdk.Rectangle(rect_x, rect_y, rect_width, rect_height)
+
+ def _do_draw_grid(self, context, rect, maximum_value, value_label_size, label_size):
+ if self.grid.get_visible():
+ return self.grid.draw(context, rect, self._mode, maximum_value, value_label_size, label_size)
+ else:
+ return rect
+
+ def _do_draw_bars(self, context, rect, maximum_value, value_label_size, label_size):
+ if self._bars == []:
+ return
+
+ #draw the bars
+ chart.init_sensitive_areas()
+ for i, bar in enumerate(self._bars):
+ bar.draw(context, rect, len(self._bars), i, self._mode, maximum_value, self._bar_padding, value_label_size, label_size, self._draw_labels)
+
+ #other methods
+ def add_bar(self, bar):
+ if bar.get_color() == COLOR_AUTO:
+ bar.set_color(COLORS[len(self._bars) % len(COLORS)])
+ self._bars.append(bar)
+ bar.connect("appearance_changed", self._cb_appearance_changed)
+
+ #callbacks
+ def _cb_motion_notify(self, widget, event):
+ if not self._mouseover: return
+ bars = chart.get_sensitive_areas(event.x, event.y)
+ if bars == []: return
+ for bar in self._bars:
+ bar.set_property("highlighted", bar in bars)
+ self.queue_draw()
+
+ def _cb_button_pressed(self, widget, event):
+ bars = chart.get_sensitive_areas(event.x, event.y)
+ for bar in bars:
+ self.emit("bar-clicked", bar)
+
+ #set and get methods
+ def set_bar_padding(self, padding):
+ """
+ Set the space between two bars in px.
+
+ @param padding: space between bars in px
+ @type padding: int in [0, 100].
+ """
+ self.set_property("bar-padding", padding)
+ self.queue_draw()
+
+ def get_bar_padding(self):
+ """
+ Returns the space between bars in px.
+
+ @return: int in [0, 100].
+ """
+ return self.get_property("bar-padding")
+
+ def set_mode(self, mode):
+ """
+ Set the mode (vertical or horizontal) of the BarChart. mode has
+ to be bar_chart.MODE_VERTICAL (default) or
+ bar_chart.MODE_HORIZONTAL.
+
+ @param mode: the new mode of the chart
+ @type mode: one of the mode constants above.
+ """
+ self.set_property("mode", mode)
+ self.queue_draw()
+
+ def get_mode(self):
+ """
+ Returns the current mode of the chart: bar_chart.MODE_VERTICAL
+ or bar_chart.MODE_HORIZONTAL.
+
+ @return: a mode constant.
+ """
+ return self.get_property("mode")
+
+ def set_draw_labels(self, draw):
+ """
+ Set whether labels should be drawn on bars.
+
+ @type draw: boolean.
+ """
+ self.set_property("draw-labels", draw)
+ self.queue_draw()
+
+ def get_draw_labels(self):
+ """
+ Returns True if labels are drawn on bars.
+
+ @return: boolean.
+ """
+ return self.get_property("draw-labels")
+
+ def set_enable_mouseover(self, mouseover):
+ """
+ Set whether a mouseover effect should be shown when the pointer
+ enters a bar.
+
+ @type mouseover: boolean.
+ """
+ self.set_property("enable-mouseover", mouseover)
+
+ def get_enable_mouseover(self):
+ """
+ Returns True if the mouseover effect is enabled.
+
+ @return: boolean.
+ """
+ return self.get_property("enable-mouseover")
+
diff --git a/pygtk_chart/bar_chart.pyc b/pygtk_chart/bar_chart.pyc
new file mode 100644
index 0000000..ef91f75
--- /dev/null
+++ b/pygtk_chart/bar_chart.pyc
Binary files differ
diff --git a/pygtk_chart/basics.py b/pygtk_chart/basics.py
new file mode 100644
index 0000000..5fdcd10
--- /dev/null
+++ b/pygtk_chart/basics.py
@@ -0,0 +1,133 @@
+#!/usr/bin/env python
+#
+# misc.py
+#
+# Copyright 2008 Sven Festersen <sven@sven-festersen.de>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+# MA 02110-1301, USA.
+"""
+This module contains simple functions needed by all other modules.
+
+Author: Sven Festersen (sven@sven-festersen.de)
+"""
+__docformat__ = "epytext"
+import cairo
+import gtk
+import os
+
+import pygtk_chart
+
+def is_in_range(x, (xmin, xmax)):
+ """
+ Use this method to test whether M{xmin <= x <= xmax}.
+
+ @type x: number
+ @type xmin: number
+ @type xmax: number
+ """
+ return (xmin <= x and xmax >= x)
+
+def intersect_ranges(range_a, range_b):
+ min_a, max_a = range_a
+ min_b, max_b = range_b
+ return max(min_a, min_b), min(max_a, max_b)
+
+def get_center(rect):
+ """
+ Find the center point of a rectangle.
+
+ @type rect: gtk.gdk.Rectangle
+ @param rect: The rectangle.
+ @return: A (x, y) tuple specifying the center point.
+ """
+ return rect.width / 2, rect.height / 2
+
+def color_gdk_to_cairo(color):
+ """
+ Convert a gtk.gdk.Color to cairo color.
+
+ @type color: gtk.gdk.Color
+ @param color: the color to convert
+ @return: a color in cairo format.
+ """
+ return (color.red / 65535.0, color.green / 65535.0, color.blue / 65535.0)
+
+def color_cairo_to_gdk(r, g, b):
+ return gtk.gdk.Color(int(65535 * r), int(65535 * g), int(65535 * b))
+
+def color_rgb_to_cairo(color):
+ """
+ Convert a 8 bit RGB value to cairo color.
+
+ @type color: a triple of integers between 0 and 255
+ @param color: The color to convert.
+ @return: A color in cairo format.
+ """
+ return (color[0] / 255.0, color[1] / 255.0, color[2] / 255.0)
+
+def color_html_to_cairo(color):
+ """
+ Convert a html (hex) RGB value to cairo color.
+
+ @type color: html color string
+ @param color: The color to convert.
+ @return: A color in cairo format.
+ """
+ if color[0] == '#':
+ color = color[1:]
+ (r, g, b) = (int(color[:2], 16),
+ int(color[2:4], 16),
+ int(color[4:], 16))
+ return color_rgb_to_cairo((r, g, b))
+
+def color_list_from_file(filename):
+ """
+ Read a file with one html hex color per line and return a list
+ of cairo colors.
+ """
+ result = []
+ if os.path.exists(filename):
+ f = open(filename, "r")
+ for line in f.readlines():
+ line = line.strip()
+ result.append(color_html_to_cairo(line))
+ return result
+
+def gdk_color_list_from_file(filename):
+ """
+ Read a file with one html hex color per line and return a list
+ of gdk colors.
+ """
+ result = []
+ if os.path.exists(filename):
+ f = open(filename, "r")
+ for line in f.readlines():
+ line = line.strip()
+ result.append(gtk.gdk.color_parse(line))
+ return result
+
+def set_context_line_style(context, style):
+ """
+ The the line style for a context.
+ """
+ if style == pygtk_chart.LINE_STYLE_SOLID:
+ context.set_dash([])
+ elif style == pygtk_chart.LINE_STYLE_DASHED:
+ context.set_dash([5])
+ elif style == pygtk_chart.LINE_STYLE_DASHED_ASYMMETRIC:
+ context.set_dash([6, 6, 2, 6])
+ elif style == pygtk_chart.LINE_STYLE_DOTTED:
+ context.set_dash([1])
diff --git a/pygtk_chart/basics.pyc b/pygtk_chart/basics.pyc
new file mode 100644
index 0000000..5b1d2e8
--- /dev/null
+++ b/pygtk_chart/basics.pyc
Binary files differ
diff --git a/pygtk_chart/chart.py b/pygtk_chart/chart.py
new file mode 100644
index 0000000..8b4a283
--- /dev/null
+++ b/pygtk_chart/chart.py
@@ -0,0 +1,592 @@
+#!/usr/bin/env python
+#
+# plot.py
+#
+# Copyright 2008 Sven Festersen <sven@sven-festersen.de>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+# MA 02110-1301, USA.
+
+"""
+Module Contents
+===============
+This is the main module. It contains the base classes for chart widgets.
+ - class Chart: base class for all chart widgets.
+ - class Background: background of a chart widget.
+ - class Title: title of a chart.
+
+Colors
+------
+All colors that pygtkChart uses are gtk.gdk.Colors as used by PyGTK.
+
+Author: Sven Festersen (sven@sven-festersen.de)
+"""
+__docformat__ = "epytext"
+import cairo
+import gobject
+import gtk
+import os
+import pango
+import pangocairo
+import pygtk
+
+from pygtk_chart.chart_object import ChartObject
+from pygtk_chart.basics import *
+from pygtk_chart import label
+
+COLOR_AUTO = 0
+AREA_CIRCLE = 0
+AREA_RECTANGLE = 1
+CLICK_SENSITIVE_AREAS = []
+
+
+def init_sensitive_areas():
+ global CLICK_SENSITIVE_AREAS
+ CLICK_SENSITIVE_AREAS = []
+
+def add_sensitive_area(type, coords, data):
+ global CLICK_SENSITIVE_AREAS
+ CLICK_SENSITIVE_AREAS.append((type, coords, data))
+
+def get_sensitive_areas(x, y):
+ res = []
+ global CLICK_SENSITIVE_AREAS
+ for type, coords, data in CLICK_SENSITIVE_AREAS:
+ if type == AREA_CIRCLE:
+ ax, ay, radius = coords
+ if (ax - x) ** 2 + (ay - y) ** 2 <= radius ** 2:
+ res.append(data)
+ elif type == AREA_RECTANGLE:
+ ax, ay, width, height = coords
+ if ax <= x <= ax + width and ay <= y <= ay + height:
+ res.append(data)
+ return res
+
+
+class Chart(gtk.DrawingArea):
+ """
+ This is the base class for all chart widgets.
+
+ Properties
+ ==========
+ The Chart class inherits properties from gtk.DrawingArea.
+ Additional properties:
+ - padding (the amount of free white space between the chart's
+ content and its border in px, type: int in [0, 100].
+
+ Signals
+ =======
+ The Chart class inherits signals from gtk.DrawingArea.
+ """
+
+ __gproperties__ = {"padding": (gobject.TYPE_INT, "padding",
+ "The chart's padding.", 0, 100, 16,
+ gobject.PARAM_READWRITE)}
+
+ def __init__(self):
+ gtk.DrawingArea.__init__(self)
+ #private properties:
+ self._padding = 16
+ #objects needed for every chart:
+ self.background = Background()
+ self.background.connect("appearance-changed", self._cb_appearance_changed)
+ self.title = Title()
+ self.title.connect("appearance-changed", self._cb_appearance_changed)
+
+ self.add_events(gtk.gdk.BUTTON_PRESS_MASK|gtk.gdk.SCROLL_MASK|gtk.gdk.POINTER_MOTION_MASK)
+ self.connect("expose_event", self._cb_expose_event)
+ self.connect("button_press_event", self._cb_button_pressed)
+ self.connect("motion-notify-event", self._cb_motion_notify)
+
+ def do_get_property(self, property):
+ if property.name == "padding":
+ return self._padding
+ else:
+ raise AttributeError, "Property %s does not exist." % property.name
+
+ def do_set_property(self, property, value):
+ if property.name == "padding":
+ self._padding = value
+ else:
+ raise AttributeError, "Property %s does not exist." % property.name
+
+ def _cb_appearance_changed(self, object):
+ """
+ This method is called after the appearance of an object changed
+ and forces a redraw.
+ """
+ self.queue_draw()
+
+ def _cb_button_pressed(self, widget, event):
+ pass
+
+ def _cb_motion_notify(self, widget, event):
+ pass
+
+ def _cb_expose_event(self, widget, event):
+ """
+ This method is called when an instance of Chart receives
+ the gtk expose_event.
+
+ @type widget: gtk.Widget
+ @param widget: The widget that received the event.
+ @type event: gtk.Event
+ @param event: The event.
+ """
+ self.context = widget.window.cairo_create()
+ self.context.rectangle(event.area.x, event.area.y, \
+ event.area.width, event.area.height)
+ self.context.clip()
+ self.draw(self.context)
+ return False
+
+ def draw_basics(self, context, rect):
+ """
+ Draw basic things that every plot has (background, title, ...).
+
+ @type context: cairo.Context
+ @param context: The context to draw on.
+ @type rect: gtk.gdk.Rectangle
+ @param rect: A rectangle representing the charts area.
+ """
+ self.background.draw(context, rect)
+ self.title.draw(context, rect)
+
+ #calculate the rectangle that's available for drawing the chart
+ title_height = self.title.get_real_dimensions()[1]
+ rect_height = int(rect.height - 3 * self._padding - title_height)
+ rect_width = int(rect.width - 2 * self._padding)
+ rect_x = int(rect.x + self._padding)
+ rect_y = int(rect.y + title_height + 2 * self._padding)
+ return gtk.gdk.Rectangle(rect_x, rect_y, rect_width, rect_height)
+
+ def draw(self, context):
+ """
+ Draw the widget. This method is called automatically. Don't call it
+ yourself. If you want to force a redrawing of the widget, call
+ the queue_draw() method.
+
+ @type context: cairo.Context
+ @param context: The context to draw on.
+ """
+ rect = self.get_allocation()
+ rect = gtk.gdk.Rectangle(0, 0, rect.width, rect.height) #transform rect to context coordinates
+ context.set_line_width(1)
+ rect = self.draw_basics(context, rect)
+
+ def export_svg(self, filename, size=None):
+ """
+ Saves the contents of the widget to svg file. The size of the image
+ will be the size of the widget.
+
+ @type filename: string
+ @param filename: The path to the file where you want the chart to be saved.
+ @type size: tuple
+ @param size: Optional parameter to give the desired height and width of the image.
+ """
+ if size is None:
+ rect = self.get_allocation()
+ width = rect.width
+ height = rect.height
+ else:
+ width, height = size
+ old_alloc = self.get_allocation
+ self.get_allocation = lambda: gtk.gdk.Rectangle(0, 0, width, height)
+ surface = cairo.SVGSurface(filename, width, height)
+ ctx = cairo.Context(surface)
+ context = pangocairo.CairoContext(ctx)
+ self.draw(context)
+ surface.finish()
+ if size is not None:
+ self.get_allocation = old_alloc
+
+ def export_png(self, filename, size=None):
+ """
+ Saves the contents of the widget to png file. The size of the image
+ will be the size of the widget.
+
+ @type filename: string
+ @param filename: The path to the file where you want the chart to be saved.
+ @type size: tuple
+ @param size: Optional parameter to give the desired height and width of the image.
+ """
+ if size is None:
+ rect = self.get_allocation()
+ width = rect.width
+ height = rect.height
+ else:
+ width, height = size
+ old_alloc = self.get_allocation
+ self.get_allocation = lambda: gtk.gdk.Rectangle(0, 0, width, height)
+ surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, width, height)
+ ctx = cairo.Context(surface)
+ context = pangocairo.CairoContext(ctx)
+ self.set_size_request(width, height)
+ self.draw(context)
+ surface.write_to_png(filename)
+ if size is not None:
+ self.get_allocation = old_alloc
+
+
+ def set_padding(self, padding):
+ """
+ Set the chart's padding.
+
+ @param padding: the padding in px
+ @type padding: int in [0, 100] (default: 16).
+ """
+ self.set_property("padding", padding)
+ self.queue_draw()
+
+ def get_padding(self):
+ """
+ Returns the chart's padding.
+
+ @return: int in [0, 100].
+ """
+ return self.get_property("padding")
+
+
+class Background(ChartObject):
+ """
+ The background of a chart.
+
+ Properties
+ ==========
+ This class inherits properties from chart_object.ChartObject.
+ Additional properties:
+ - color (the background color, type: gtk.gdk.Color)
+ - gradient (the background gradient, type: a pair of gtk.gdk.Color)
+ - image (path to the background image file, type: string)
+
+ Signals
+ =======
+ The Background class inherits signals from chart_object.ChartObject.
+ """
+
+ __gproperties__ = {"color": (gobject.TYPE_PYOBJECT,
+ "background color",
+ "The color of the backround.",
+ gobject.PARAM_READWRITE),
+ "gradient": (gobject.TYPE_PYOBJECT,
+ "background gradient",
+ "A background gardient. (first_color, second_color)",
+ gobject.PARAM_READWRITE),
+ "image": (gobject.TYPE_STRING,
+ "background image file",
+ "Path to the image file to use as background.",
+ "", gobject.PARAM_READWRITE)}
+
+ def __init__(self):
+ ChartObject.__init__(self)
+ self._color = gtk.gdk.color_parse("#ffffff") #the backgound is filled white by default
+ self._gradient = None
+ self._image = ""
+ self._pixbuf = None
+
+ def do_get_property(self, property):
+ if property.name == "visible":
+ return self._show
+ elif property.name == "antialias":
+ return self._antialias
+ elif property.name == "gradient":
+ return self._gradient
+ elif property.name == "color":
+ return self._color
+ elif property.name == "image":
+ return self._image
+ else:
+ raise AttributeError, "Property %s does not exist." % property.name
+
+ def do_set_property(self, property, value):
+ if property.name == "visible":
+ self._show = value
+ elif property.name == "antialias":
+ self._antialias = value
+ elif property.name == "gradient":
+ self._gradient = value
+ elif property.name == "color":
+ self._color = value
+ elif property.name == "image":
+ self._image = value
+ else:
+ raise AttributeError, "Property %s does not exist." % property.name
+
+ def _do_draw(self, context, rect):
+ """
+ Do all the drawing stuff.
+
+ @type context: cairo.Context
+ @param context: The context to draw on.
+ @type rect: gtk.gdk.Rectangle
+ @param rect: A rectangle representing the charts area.
+ """
+ if self._color != None:
+ #set source color
+ context.set_source_rgb(*color_gdk_to_cairo(self._color))
+ elif self._gradient != None:
+ #set source gradient
+ cs = color_gdk_to_cairo(self._gradient[0])
+ ce = color_gdk_to_cairo(self._gradient[1])
+ gradient = cairo.LinearGradient(0, 0, 0, rect.height)
+ gradient.add_color_stop_rgb(0, cs[0], cs[1], cs[2])
+ gradient.add_color_stop_rgb(1, ce[0], ce[1], ce[2])
+ context.set_source(gradient)
+ elif self._pixbuf:
+ context.set_source_pixbuf(self._pixbuf, 0, 0)
+ else:
+ context.set_source_rgb(1, 1, 1) #fallback to white bg
+ #create the background rectangle and fill it:
+ context.rectangle(0, 0, rect.width, rect.height)
+ context.fill()
+
+ def set_color(self, color):
+ """
+ The set_color() method can be used to change the color of the
+ background.
+
+ @type color: gtk.gdk.Color
+ @param color: Set the background to be filles with this color.
+ """
+ self.set_property("color", color)
+ self.set_property("gradient", None)
+ self.set_property("image", "")
+ self.emit("appearance_changed")
+
+ def get_color(self):
+ """
+ Returns the background's color.
+
+ @return: gtk.gdk.Color.
+ """
+ return self.get_property("color")
+
+ def set_gradient(self, color_start, color_end):
+ """
+ Use set_gradient() to define a vertical gradient as the background.
+
+ @type color_start: gtk.gdk.Color
+ @param color_start: The starting (top) color of the gradient.
+ @type color_end: gtk.gdk.Color
+ @param color_end: The ending (bottom) color of the gradient.
+ """
+ self.set_property("color", None)
+ self.set_property("gradient", (color_start, color_end))
+ self.set_property("image", "")
+ self.emit("appearance_changed")
+
+ def get_gradient(self):
+ """
+ Returns the gradient of the background or None.
+
+ @return: A (gtk.gdk.Color, gtk.gdk.Color) tuple or None.
+ """
+ return self.get_property("gradient")
+
+ def set_image(self, filename):
+ """
+ The set_image() method sets the background to be filled with an
+ image.
+
+ @type filename: string
+ @param filename: Path to the file you want to use as background
+ image. If the file does not exists, the background is set to white.
+ """
+ try:
+ self._pixbuf = gtk.gdk.pixbuf_new_from_file(filename)
+ except:
+ self._pixbuf = None
+
+ self.set_property("color", None)
+ self.set_property("gradient", None)
+ self.set_property("image", filename)
+ self.emit("appearance_changed")
+
+ def get_image(self):
+ return self.get_property("image")
+
+
+class Title(label.Label):
+ """
+ The title of a chart. The title will be drawn centered at the top of the
+ chart.
+
+ Properties
+ ==========
+ The Title class inherits properties from label.Label.
+
+ Signals
+ =======
+ The Title class inherits signals from label.Label.
+ """
+
+ def __init__(self, text=""):
+ label.Label.__init__(self, (0, 0), text, weight=pango.WEIGHT_BOLD, anchor=label.ANCHOR_TOP_CENTER, fixed=True)
+
+ def _do_draw(self, context, rect, top=-1):
+ if top == -1: top = rect.height / 80
+ self._size = max(8, int(rect.height / 50.0))
+ self._position = rect.width / 2, top
+ self._do_draw_label(context, rect)
+
+
+class Area(ChartObject):
+ """
+ This is a base class for classes that represent areas, e.g. the
+ pie_chart.PieArea class and the bar_chart.Bar class.
+
+ Properties
+ ==========
+ The Area class inherits properties from chart_object.ChartObject.
+ Additional properties:
+ - name (a unique name for the area, type: string, read only)
+ - value (the value of the area, type: float)
+ - color (the area's color, type: gtk.gdk.Color)
+ - label (a label for the area, type: string)
+ - highlighted (set whether the area should be highlighted,
+ type: boolean).
+
+ Signals
+ =======
+ The Area class inherits signals from chart_object.ChartObject.
+ """
+
+ __gproperties__ = {"name": (gobject.TYPE_STRING, "area name",
+ "A unique name for the area.",
+ "", gobject.PARAM_READABLE),
+ "value": (gobject.TYPE_FLOAT,
+ "value",
+ "The value.",
+ 0.0, 9999999999.0, 0.0, gobject.PARAM_READWRITE),
+ "color": (gobject.TYPE_PYOBJECT, "area color",
+ "The color of the area.",
+ gobject.PARAM_READWRITE),
+ "label": (gobject.TYPE_STRING, "area label",
+ "The label for the area.", "",
+ gobject.PARAM_READWRITE),
+ "highlighted": (gobject.TYPE_BOOLEAN, "area is higlighted",
+ "Set whether the area should be higlighted.",
+ False, gobject.PARAM_READWRITE)}
+
+ def __init__(self, name, value, title=""):
+ ChartObject.__init__(self)
+ self._name = name
+ self._value = value
+ self._label = title
+ self._color = COLOR_AUTO
+ self._highlighted = False
+
+ def do_get_property(self, property):
+ if property.name == "visible":
+ return self._show
+ elif property.name == "antialias":
+ return self._antialias
+ elif property.name == "name":
+ return self._name
+ elif property.name == "value":
+ return self._value
+ elif property.name == "color":
+ return self._color
+ elif property.name == "label":
+ return self._label
+ elif property.name == "highlighted":
+ return self._highlighted
+ else:
+ raise AttributeError, "Property %s does not exist." % property.name
+
+ def do_set_property(self, property, value):
+ if property.name == "visible":
+ self._show = value
+ elif property.name == "antialias":
+ self._antialias = value
+ elif property.name == "value":
+ self._value = value
+ elif property.name == "color":
+ self._color = value
+ elif property.name == "label":
+ self._label = value
+ elif property.name == "highlighted":
+ self._highlighted = value
+ else:
+ raise AttributeError, "Property %s does not exist." % property.name
+
+ def set_value(self, value):
+ """
+ Set the value of the area.
+
+ @type value: float.
+ """
+ self.set_property("value", value)
+ self.emit("appearance_changed")
+
+ def get_value(self):
+ """
+ Returns the current value of the area.
+
+ @return: float.
+ """
+ return self.get_property("value")
+
+ def set_color(self, color):
+ """
+ Set the color of the area.
+
+ @type color: gtk.gdk.Color.
+ """
+ self.set_property("color", color)
+ self.emit("appearance_changed")
+
+ def get_color(self):
+ """
+ Returns the current color of the area or COLOR_AUTO.
+
+ @return: gtk.gdk.Color or COLOR_AUTO.
+ """
+ return self.get_property("color")
+
+ def set_label(self, label):
+ """
+ Set the label for the area.
+
+ @param label: the new label
+ @type label: string.
+ """
+ self.set_property("label", label)
+ self.emit("appearance_changed")
+
+ def get_label(self):
+ """
+ Returns the current label of the area.
+
+ @return: string.
+ """
+ return self.get_property("label")
+
+ def set_highlighted(self, highlighted):
+ """
+ Set whether the area should be highlighted.
+
+ @type highlighted: boolean.
+ """
+ self.set_property("highlighted", highlighted)
+ self.emit("appearance_changed")
+
+ def get_highlighted(self):
+ """
+ Returns True if the area is currently highlighted.
+
+ @return: boolean.
+ """
+ return self.get_property("highlighted")
diff --git a/pygtk_chart/chart.pyc b/pygtk_chart/chart.pyc
new file mode 100644
index 0000000..a098bcf
--- /dev/null
+++ b/pygtk_chart/chart.pyc
Binary files differ
diff --git a/pygtk_chart/chart_object.py b/pygtk_chart/chart_object.py
new file mode 100644
index 0000000..8e4cd7e
--- /dev/null
+++ b/pygtk_chart/chart_object.py
@@ -0,0 +1,155 @@
+#!/usr/bin/env python
+#
+# chart_object.py
+#
+# Copyright 2009 Sven Festersen <sven@sven-festersen.de>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+# MA 02110-1301, USA.
+"""
+This module contains the ChartObject class.
+
+Author: Sven Festersen (sven@sven-festersen.de)
+"""
+import cairo
+import gobject
+
+class ChartObject(gobject.GObject):
+ """
+ This is the base class for all things that can be drawn on a chart
+ widget.
+ It emits the signal 'appearance-changed' when it needs to be
+ redrawn.
+
+ Properties
+ ==========
+ ChartObject inherits properties from gobject.GObject.
+ Additional properties:
+ - visible (sets whether the object should be visible,
+ type: boolean)
+ - antialias (sets whether the object should be antialiased,
+ type: boolean).
+
+ Signals
+ =======
+ ChartObject inherits signals from gobject.GObject,
+ Additional signals:
+ - appearance-changed (emitted if the object needs to be redrawn).
+ """
+
+ __gsignals__ = {"appearance-changed": (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, [])}
+
+
+ __gproperties__ = {"visible": (gobject.TYPE_BOOLEAN,
+ "visibilty of the object",
+ "Set whether to draw the object or not.",
+ True, gobject.PARAM_READWRITE),
+ "antialias": (gobject.TYPE_BOOLEAN,
+ "use antialiasing",
+ "Set whether to use antialiasing when drawing the object.",
+ True, gobject.PARAM_READWRITE)}
+
+ def __init__(self):
+ gobject.GObject.__init__(self)
+ self._show = True
+ self._antialias = True
+
+ def do_get_property(self, property):
+ if property.name == "visible":
+ return self._show
+ elif property.name == "antialias":
+ return self._antialias
+ else:
+ raise AttributeError, "Property %s does not exist." % property.name
+
+ def do_set_property(self, property, value):
+ if property.name == "visible":
+ self._show = value
+ elif property.name == "antialias":
+ self._antialias = value
+ else:
+ raise AttributeError, "Property %s does not exist." % property.name
+
+ def _do_draw(self, context, rect):
+ """
+ A derived class should override this method. The drawing stuff
+ should happen here.
+
+ @type context: cairo.Context
+ @param context: The context to draw on.
+ @type rect: gtk.gdk.Rectangle
+ @param rect: A rectangle representing the charts area.
+ """
+ pass
+
+ def draw(self, context, rect, *args):
+ """
+ This method is called by the parent Chart instance. It
+ calls _do_draw.
+
+ @type context: cairo.Context
+ @param context: The context to draw on.
+ @type rect: gtk.gdk.Rectangle
+ @param rect: A rectangle representing the charts area.
+ """
+ res = None
+ if self._show:
+ if not self._antialias:
+ context.set_antialias(cairo.ANTIALIAS_NONE)
+ res = self._do_draw(context, rect, *args)
+ context.set_antialias(cairo.ANTIALIAS_DEFAULT)
+ return res
+
+ def set_antialias(self, antialias):
+ """
+ This method sets the antialiasing mode of the ChartObject. Antialiasing
+ is enabled by default.
+
+ @type antialias: boolean
+ @param antialias: If False, antialiasing is disabled for this
+ ChartObject.
+ """
+ self.set_property("antialias", antialias)
+ self.emit("appearance_changed")
+
+ def get_antialias(self):
+ """
+ Returns True if antialiasing is enabled for the object.
+
+ @return: boolean.
+ """
+ return self.get_property("antialias")
+
+ def set_visible(self, visible):
+ """
+ Use this method to set whether the ChartObject should be visible or
+ not.
+
+ @type visible: boolean
+ @param visible: If False, the PlotObject won't be drawn.
+ """
+ self.set_property("visible", visible)
+ self.emit("appearance_changed")
+
+ def get_visible(self):
+ """
+ Returns True if the object is visble.
+
+ @return: boolean.
+ """
+ return self.get_property("visible")
+
+
+gobject.type_register(ChartObject)
diff --git a/pygtk_chart/chart_object.pyc b/pygtk_chart/chart_object.pyc
new file mode 100644
index 0000000..df4f462
--- /dev/null
+++ b/pygtk_chart/chart_object.pyc
Binary files differ
diff --git a/pygtk_chart/data/tango.color b/pygtk_chart/data/tango.color
new file mode 100644
index 0000000..6231528
--- /dev/null
+++ b/pygtk_chart/data/tango.color
@@ -0,0 +1,7 @@
+#cc0000
+#3465a4
+#73d216
+#f57900
+#75507b
+#c17d11
+#edd400
diff --git a/pygtk_chart/label.py b/pygtk_chart/label.py
new file mode 100644
index 0000000..75b523c
--- /dev/null
+++ b/pygtk_chart/label.py
@@ -0,0 +1,744 @@
+#!/usr/bin/env python
+#
+# text.py
+#
+# Copyright 2009 Sven Festersen <sven@sven-festersen.de>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+# MA 02110-1301, USA.
+"""
+Contains the Label class.
+
+Author: Sven Festersen (sven@sven-festersen.de)
+"""
+import cairo
+import gobject
+import gtk
+import math
+import pango
+import pygtk
+
+from pygtk_chart import basics
+from pygtk_chart.chart_object import ChartObject
+
+
+ANCHOR_BOTTOM_LEFT = 0
+ANCHOR_TOP_LEFT = 1
+ANCHOR_TOP_RIGHT = 2
+ANCHOR_BOTTOM_RIGHT = 4
+ANCHOR_CENTER = 5
+ANCHOR_TOP_CENTER = 6
+ANCHOR_BOTTOM_CENTER = 7
+ANCHOR_LEFT_CENTER = 8
+ANCHOR_RIGHT_CENTER = 9
+
+UNDERLINE_NONE = pango.UNDERLINE_NONE
+UNDERLINE_SINGLE = pango.UNDERLINE_SINGLE
+UNDERLINE_DOUBLE = pango.UNDERLINE_DOUBLE
+UNDERLINE_LOW = pango.UNDERLINE_LOW
+
+STYLE_NORMAL = pango.STYLE_NORMAL
+STYLE_OBLIQUE = pango.STYLE_OBLIQUE
+STYLE_ITALIC = pango.STYLE_ITALIC
+
+WEIGHT_ULTRALIGHT = pango.WEIGHT_ULTRALIGHT
+WEIGHT_LIGHT = pango.WEIGHT_LIGHT
+WEIGHT_NORMAL = pango.WEIGHT_NORMAL
+WEIGHT_BOLD = pango.WEIGHT_BOLD
+WEIGHT_ULTRABOLD = pango.WEIGHT_ULTRABOLD
+WEIGHT_HEAVY = pango.WEIGHT_HEAVY
+
+
+DRAWING_INITIALIZED = False
+REGISTERED_LABELS = []
+
+
+def begin_drawing():
+ global DRAWING_INITIALIZED
+ DRAWING_INITIALIZED = True
+
+def finish_drawing():
+ global REGISTERED_LABELS
+ global DRAWING_INITIALIZED
+ REGISTERED_LABELS = []
+ DRAWING_INITIALIZED = False
+
+def register_label(label):
+ if DRAWING_INITIALIZED:
+ REGISTERED_LABELS.append(label)
+
+def get_registered_labels():
+ if DRAWING_INITIALIZED:
+ return REGISTERED_LABELS
+ return []
+
+
+class Label(ChartObject):
+ """
+ This class is used for drawing all the text on the chart widgets.
+ It uses the pango layout engine.
+
+ Properties
+ ==========
+ The Label class inherits properties from chart_object.ChartObject.
+ Additional properties:
+ - color (the label's color, type: gtk.gdk.Color)
+ - text (text to display, type: string)
+ - position (the label's position, type: pair of float)
+ - anchor (the anchor that should be used to position the label,
+ type: an anchor constant)
+ - underline (sets the type of underline, type; an underline
+ constant)
+ - max-width (the maximum width of the label in px, type: int)
+ - rotation (angle of rotation in degrees, type: int)
+ - size (the size of the label's text in px, type: int)
+ - slant (the font slant, type: a slant style constant)
+ - weight (the font weight, type: a font weight constant)
+ - fixed (sets whether the position of the label may be changed
+ dynamicly or not, type: boolean)
+ - wrap (sets whether the label's text should be wrapped if it's
+ longer than max-width, type: boolean).
+
+ Signals
+ =======
+ The Label class inherits signals from chart_object.ChartObject.
+ """
+
+ __gproperties__ = {"color": (gobject.TYPE_PYOBJECT,
+ "label color",
+ "The color of the label (a gtk.gdkColor)",
+ gobject.PARAM_READWRITE),
+ "text": (gobject.TYPE_STRING,
+ "label text",
+ "The text to show on the label.",
+ "", gobject.PARAM_READWRITE),
+ "position": (gobject.TYPE_PYOBJECT,
+ "label position",
+ "A pair of x,y coordinates.",
+ gobject.PARAM_READWRITE),
+ "anchor": (gobject.TYPE_INT, "label anchor",
+ "The anchor of the label.", 0, 9, 0,
+ gobject.PARAM_READWRITE),
+ "underline": (gobject.TYPE_PYOBJECT,
+ "underline text",
+ "Set whether to underline the text.",
+ gobject.PARAM_READWRITE),
+ "max-width": (gobject.TYPE_INT, "maximum width",
+ "The maximum width of the label.",
+ 1, 99999, 99999,
+ gobject.PARAM_READWRITE),
+ "rotation": (gobject.TYPE_INT, "rotation of the label",
+ "The angle that the label should be rotated by in degrees.",
+ 0, 360, 0, gobject.PARAM_READWRITE),
+ "size": (gobject.TYPE_INT, "text size",
+ "The size of the text.", 0, 1000, 8,
+ gobject.PARAM_READWRITE),
+ "slant": (gobject.TYPE_PYOBJECT, "font slant",
+ "The font slant style.",
+ gobject.PARAM_READWRITE),
+ "weight": (gobject.TYPE_PYOBJECT, "font weight",
+ "The font weight.", gobject.PARAM_READWRITE),
+ "fixed": (gobject.TYPE_BOOLEAN, "fixed",
+ "Set whether the position of the label should be forced.",
+ False, gobject.PARAM_READWRITE),
+ "wrap": (gobject.TYPE_BOOLEAN, "wrap text",
+ "Set whether text should be wrapped.",
+ False, gobject.PARAM_READWRITE)}
+
+ def __init__(self, position, text, size=None,
+ slant=pango.STYLE_NORMAL,
+ weight=pango.WEIGHT_NORMAL,
+ underline=pango.UNDERLINE_NONE,
+ anchor=ANCHOR_BOTTOM_LEFT, max_width=99999,
+ fixed=False):
+ ChartObject.__init__(self)
+ self._position = position
+ self._text = text
+ self._size = size
+ self._slant = slant
+ self._weight = weight
+ self._underline = underline
+ self._anchor = anchor
+ self._rotation = 0
+ self._color = gtk.gdk.Color()
+ self._max_width = max_width
+ self._fixed = fixed
+ self._wrap = True
+
+ self._real_dimensions = (0, 0)
+ self._real_position = (0, 0)
+ self._line_count = 1
+
+ self._context = None
+ self._layout = None
+
+ def do_get_property(self, property):
+ if property.name == "visible":
+ return self._show
+ elif property.name == "antialias":
+ return self._antialias
+ elif property.name == "text":
+ return self._text
+ elif property.name == "color":
+ return self._color
+ elif property.name == "position":
+ return self._position
+ elif property.name == "anchor":
+ return self._anchor
+ elif property.name == "underline":
+ return self._underline
+ elif property.name == "max-width":
+ return self._max_width
+ elif property.name == "rotation":
+ return self._rotation
+ elif property.name == "size":
+ return self._size
+ elif property.name == "slant":
+ return self._slant
+ elif property.name == "weight":
+ return self._weight
+ elif property.name == "fixed":
+ return self._fixed
+ elif property.name == "wrap":
+ return self._wrap
+ else:
+ raise AttributeError, "Property %s does not exist." % property.name
+
+ def do_set_property(self, property, value):
+ if property.name == "visible":
+ self._show = value
+ elif property.name == "antialias":
+ self._antialias = value
+ elif property.name == "text":
+ self._text = value
+ elif property.name == "color":
+ self._color = value
+ elif property.name == "position":
+ self._position = value
+ elif property.name == "anchor":
+ self._anchor = value
+ elif property.name == "underline":
+ self._underline = value
+ elif property.name == "max-width":
+ self._max_width = value
+ elif property.name == "rotation":
+ self._rotation = value
+ elif property.name == "size":
+ self._size = value
+ elif property.name == "slant":
+ self._slant = value
+ elif property.name == "weight":
+ self._weight = value
+ elif property.name == "fixed":
+ self._fixed = value
+ elif property.name == "wrap":
+ self._wrap = value
+ else:
+ raise AttributeError, "Property %s does not exist." % property.name
+
+ def _do_draw(self, context, rect):
+ self._do_draw_label(context, rect)
+
+ def _do_draw_label(self, context, rect):
+ angle = 2 * math.pi * self._rotation / 360.0
+
+ if self._context == None:
+ label = gtk.Label()
+ self._context = label.create_pango_context()
+ pango_context = self._context
+
+ attrs = pango.AttrList()
+ attrs.insert(pango.AttrWeight(self._weight, 0, len(self._text)))
+ attrs.insert(pango.AttrStyle(self._slant, 0, len(self._text)))
+ attrs.insert(pango.AttrUnderline(self._underline, 0,
+ len(self._text)))
+ if self._size != None:
+ attrs.insert(pango.AttrSize(1000 * self._size, 0,
+ len(self._text)))
+
+ if self._layout == None:
+ self._layout = pango.Layout(pango_context)
+ layout = self._layout
+ layout.set_text(self._text)
+ layout.set_attributes(attrs)
+
+ #find out where to draw the layout and calculate the maximum width
+ width = rect.width
+ if self._anchor in [ANCHOR_BOTTOM_LEFT, ANCHOR_TOP_LEFT,
+ ANCHOR_LEFT_CENTER]:
+ width = rect.width - self._position[0]
+ elif self._anchor in [ANCHOR_BOTTOM_RIGHT, ANCHOR_TOP_RIGHT,
+ ANCHOR_RIGHT_CENTER]:
+ width = self._position[0]
+
+ text_width, text_height = layout.get_pixel_size()
+ width = width * math.cos(angle)
+ width = min(width, self._max_width)
+
+ if self._wrap:
+ layout.set_wrap(pango.WRAP_WORD_CHAR)
+ layout.set_width(int(1000 * width))
+
+ x, y = get_text_pos(layout, self._position, self._anchor, angle)
+
+ if not self._fixed:
+ #Find already drawn labels that would intersect with the current one
+ #and adjust position to avoid intersection.
+ text_width, text_height = layout.get_pixel_size()
+ real_width = abs(text_width * math.cos(angle)) + abs(text_height * math.sin(angle))
+ real_height = abs(text_height * math.cos(angle)) + abs(text_width * math.sin(angle))
+
+ other_labels = get_registered_labels()
+ this_rect = gtk.gdk.Rectangle(int(x), int(y), int(real_width), int(real_height))
+ for label in other_labels:
+ label_rect = label.get_allocation()
+ intersection = this_rect.intersect(label_rect)
+ if intersection.width == 0 and intersection.height == 0:
+ continue
+
+ y_diff = 0
+ if label_rect.y <= y and label_rect.y + label_rect.height >= y:
+ y_diff = y - label_rect.y + label_rect.height
+ elif label_rect.y > y and label_rect.y < y + real_height:
+ y_diff = label_rect.y - real_height - y
+ y += y_diff
+
+ #draw layout
+ context.move_to(x, y)
+ context.rotate(angle)
+ context.set_source_rgb(*basics.color_gdk_to_cairo(self._color))
+ context.show_layout(layout)
+ context.rotate(-angle)
+ context.stroke()
+
+ #calculate the real dimensions
+ text_width, text_height = layout.get_pixel_size()
+ real_width = abs(text_width * math.cos(angle)) + abs(text_height * math.sin(angle))
+ real_height = abs(text_height * math.cos(angle)) + abs(text_width * math.sin(angle))
+ self._real_dimensions = real_width, real_height
+ self._real_position = x, y
+ self._line_count = layout.get_line_count()
+
+ register_label(self)
+
+ def get_calculated_dimensions(self, context, rect):
+ angle = 2 * math.pi * self._rotation / 360.0
+
+ if self._context == None:
+ label = gtk.Label()
+ self._context = label.create_pango_context()
+ pango_context = self._context
+
+ attrs = pango.AttrList()
+ attrs.insert(pango.AttrWeight(self._weight, 0, len(self._text)))
+ attrs.insert(pango.AttrStyle(self._slant, 0, len(self._text)))
+ attrs.insert(pango.AttrUnderline(self._underline, 0,
+ len(self._text)))
+ if self._size != None:
+ attrs.insert(pango.AttrSize(1000 * self._size, 0,
+ len(self._text)))
+
+ if self._layout == None:
+ self._layout = pango.Layout(pango_context)
+ layout = self._layout
+
+ layout.set_text(self._text)
+ layout.set_attributes(attrs)
+
+ #find out where to draw the layout and calculate the maximum width
+ width = rect.width
+ if self._anchor in [ANCHOR_BOTTOM_LEFT, ANCHOR_TOP_LEFT,
+ ANCHOR_LEFT_CENTER]:
+ width = rect.width - self._position[0]
+ elif self._anchor in [ANCHOR_BOTTOM_RIGHT, ANCHOR_TOP_RIGHT,
+ ANCHOR_RIGHT_CENTER]:
+ width = self._position[0]
+
+ text_width, text_height = layout.get_pixel_size()
+ width = width * math.cos(angle)
+ width = min(width, self._max_width)
+
+ if self._wrap:
+ layout.set_wrap(pango.WRAP_WORD_CHAR)
+ layout.set_width(int(1000 * width))
+
+ x, y = get_text_pos(layout, self._position, self._anchor, angle)
+
+ if not self._fixed:
+ #Find already drawn labels that would intersect with the current one
+ #and adjust position to avoid intersection.
+ text_width, text_height = layout.get_pixel_size()
+ real_width = abs(text_width * math.cos(angle)) + abs(text_height * math.sin(angle))
+ real_height = abs(text_height * math.cos(angle)) + abs(text_width * math.sin(angle))
+
+ other_labels = get_registered_labels()
+ this_rect = gtk.gdk.Rectangle(int(x), int(y), int(real_width), int(real_height))
+ for label in other_labels:
+ label_rect = label.get_allocation()
+ intersection = this_rect.intersect(label_rect)
+ if intersection.width == 0 and intersection.height == 0:
+ continue
+
+ y_diff = 0
+ if label_rect.y <= y and label_rect.y + label_rect.height >= y:
+ y_diff = y - label_rect.y + label_rect.height
+ elif label_rect.y > y and label_rect.y < y + real_height:
+ y_diff = label_rect.y - real_height - y
+ y += y_diff
+
+ #calculate the dimensions
+ text_width, text_height = layout.get_pixel_size()
+ real_width = abs(text_width * math.cos(angle)) + abs(text_height * math.sin(angle))
+ real_height = abs(text_height * math.cos(angle)) + abs(text_width * math.sin(angle))
+ return real_width, real_height
+
+ def set_text(self, text):
+ """
+ Use this method to set the text that should be displayed by
+ the label.
+
+ @param text: the text to display.
+ @type text: string
+ """
+ self.set_property("text", text)
+ self.emit("appearance_changed")
+
+ def get_text(self):
+ """
+ Returns the text currently displayed.
+
+ @return: string.
+ """
+ return self.get_property("text")
+
+ def set_color(self, color):
+ """
+ Set the color of the label. color has to be a gtk.gdk.Color.
+
+ @param color: the color of the label
+ @type color: gtk.gdk.Color.
+ """
+ self.set_property("color", color)
+ self.emit("appearance_changed")
+
+ def get_color(self):
+ """
+ Returns the current color of the label.
+
+ @return: gtk.gdk.Color.
+ """
+ return self.get_property("color")
+
+ def set_position(self, pos):
+ """
+ Set the position of the label. pos has to be a x,y pair of
+ absolute pixel coordinates on the widget.
+ The position is not the actual position but the position of the
+ Label's anchor point (see L{set_anchor} for details).
+
+ @param pos: new position of the label
+ @type pos: pair of (x, y).
+ """
+ self.set_property("position", pos)
+ self.emit("appearance_changed")
+
+ def get_position(self):
+ """
+ Returns the current position of the label.
+
+ @return: pair of (x, y).
+ """
+ return self.get_property("position")
+
+ def set_anchor(self, anchor):
+ """
+ Set the anchor point of the label. The anchor point is the a
+ point on the label's edge that has the position you set with
+ set_position().
+ anchor has to be one of the following constants:
+
+ - label.ANCHOR_BOTTOM_LEFT
+ - label.ANCHOR_TOP_LEFT
+ - label.ANCHOR_TOP_RIGHT
+ - label.ANCHOR_BOTTOM_RIGHT
+ - label.ANCHOR_CENTER
+ - label.ANCHOR_TOP_CENTER
+ - label.ANCHOR_BOTTOM_CENTER
+ - label.ANCHOR_LEFT_CENTER
+ - label.ANCHOR_RIGHT_CENTER
+
+ The meaning of the constants is illustrated below:::
+
+
+ ANCHOR_TOP_LEFT ANCHOR_TOP_CENTER ANCHOR_TOP_RIGHT
+ * * *
+ #####################
+ ANCHOR_LEFT_CENTER * # * # * ANCHOR_RIGHT_CENTER
+ #####################
+ * * *
+ ANCHOR_BOTTOM_LEFT ANCHOR_BOTTOM_CENTER ANCHOR_BOTTOM_RIGHT
+
+ The point in the center is of course referred to by constant
+ label.ANCHOR_CENTER.
+
+ @param anchor: the anchor point of the label
+ @type anchor: one of the constants described above.
+ """
+
+ self.set_property("anchor", anchor)
+ self.emit("appearance_changed")
+
+ def get_anchor(self):
+ """
+ Returns the current anchor point that's used to position the
+ label. See L{set_anchor} for details.
+
+ @return: one of the anchor constants described in L{set_anchor}.
+ """
+ return self.get_property("anchor")
+
+ def set_underline(self, underline):
+ """
+ Set the underline style of the label. underline has to be one
+ of the following constants:
+
+ - label.UNDERLINE_NONE: do not underline the text
+ - label.UNDERLINE_SINGLE: draw a single underline (the normal
+ underline method)
+ - label.UNDERLINE_DOUBLE: draw a double underline
+ - label.UNDERLINE_LOW; draw a single low underline.
+
+ @param underline: the underline style
+ @type underline: one of the constants above.
+ """
+ self.set_property("underline", underline)
+ self.emit("appearance_changed")
+
+ def get_underline(self):
+ """
+ Returns the current underline style. See L{set_underline} for
+ details.
+
+ @return: an underline constant (see L{set_underline}).
+ """
+ return self.get_property("underline")
+
+ def set_max_width(self, width):
+ """
+ Set the maximum width of the label in pixels.
+
+ @param width: the maximum width
+ @type width: integer.
+ """
+ self.set_property("max-width", width)
+ self.emit("appearance_changed")
+
+ def get_max_width(self):
+ """
+ Returns the maximum width of the label.
+
+ @return: integer.
+ """
+ return self.get_property("max-width")
+
+ def set_rotation(self, angle):
+ """
+ Use this method to set the rotation of the label in degrees.
+
+ @param angle: the rotation angle
+ @type angle: integer in [0, 360].
+ """
+ self.set_property("rotation", angle)
+ self.emit("appearance_changed")
+
+ def get_rotation(self):
+ """
+ Returns the current rotation angle.
+
+ @return: integer in [0, 360].
+ """
+ return self.get_property("rotation")
+
+ def set_size(self, size):
+ """
+ Set the size of the text in pixels.
+
+ @param size: size of the text
+ @type size: integer.
+ """
+ self.set_property("size", size)
+ self.emit("appearance_changed")
+
+ def get_size(self):
+ """
+ Returns the current size of the text in pixels.
+
+ @return: integer.
+ """
+ return self.get_property("size")
+
+ def set_slant(self, slant):
+ """
+ Set the font slant. slat has to be one of the following:
+
+ - label.STYLE_NORMAL
+ - label.STYLE_OBLIQUE
+ - label.STYLE_ITALIC
+
+ @param slant: the font slant style
+ @type slant: one of the constants above.
+ """
+ self.set_property("slant", slant)
+ self.emit("appearance_changed")
+
+ def get_slant(self):
+ """
+ Returns the current font slant style. See L{set_slant} for
+ details.
+
+ @return: a slant style constant.
+ """
+ return self.get_property("slant")
+
+ def set_weight(self, weight):
+ """
+ Set the font weight. weight has to be one of the following:
+
+ - label.WEIGHT_ULTRALIGHT
+ - label.WEIGHT_LIGHT
+ - label.WEIGHT_NORMAL
+ - label.WEIGHT_BOLD
+ - label.WEIGHT_ULTRABOLD
+ - label.WEIGHT_HEAVY
+
+ @param weight: the font weight
+ @type weight: one of the constants above.
+ """
+ self.set_property("weight", weight)
+ self.emit("appearance_changed")
+
+ def get_weight(self):
+ """
+ Returns the current font weight. See L{set_weight} for details.
+
+ @return: a font weight constant.
+ """
+ return self.get_property("weight")
+
+ def set_fixed(self, fixed):
+ """
+ Set whether the position of the label should be forced
+ (fixed=True) or if it should be positioned avoiding intersection
+ with other labels.
+
+ @type fixed: boolean.
+ """
+ self.set_property("fixed", fixed)
+ self.emit("appearance_changed")
+
+ def get_fixed(self):
+ """
+ Returns True if the label's position is forced.
+
+ @return: boolean
+ """
+ return self.get_property("fixed")
+
+ def set_wrap(self, wrap):
+ """
+ Set whether too long text should be wrapped.
+
+ @type wrap: boolean.
+ """
+ self.set_property("wrap", wrap)
+ self.emit("appearance_changed")
+
+ def get_wrap(self):
+ """
+ Returns True if too long text should be wrapped.
+
+ @return: boolean.
+ """
+ return self.get_property("wrap")
+
+ def get_real_dimensions(self):
+ """
+ This method returns a pair (width, height) with the dimensions
+ the label was drawn with. Call this method I{after} drawing
+ the label.
+
+ @return: a (width, height) pair.
+ """
+ return self._real_dimensions
+
+ def get_real_position(self):
+ """
+ Returns the position of the label where it was really drawn.
+
+ @return: a (x, y) pair.
+ """
+ return self._real_position
+
+ def get_allocation(self):
+ """
+ Returns an allocation rectangle.
+
+ @return: gtk.gdk.Rectangle.
+ """
+ x, y = self._real_position
+ w, h = self._real_dimensions
+ return gtk.gdk.Rectangle(int(x), int(y), int(w), int(h))
+
+ def get_line_count(self):
+ """
+ Returns the number of lines.
+
+ @return: int.
+ """
+ return self._line_count
+
+
+def get_text_pos(layout, pos, anchor, angle):
+ """
+ This function calculates the position of bottom left point of the
+ layout respecting the given anchor point.
+
+ @return: (x, y) pair
+ """
+ text_width_n, text_height_n = layout.get_pixel_size()
+ text_width = text_width_n * abs(math.cos(angle)) + text_height_n * abs(math.sin(angle))
+ text_height = text_height_n * abs(math.cos(angle)) + text_width_n * abs(math.sin(angle))
+ height_delta = text_height - text_height_n
+ x, y = pos
+ ref = (0, -text_height)
+ if anchor == ANCHOR_TOP_LEFT:
+ ref = (0, 0)
+ elif anchor == ANCHOR_TOP_RIGHT:
+ ref = (-text_width, height_delta)
+ elif anchor == ANCHOR_BOTTOM_RIGHT:
+ ref = (-text_width, -text_height)
+ elif anchor == ANCHOR_CENTER:
+ ref = (-text_width / 2, -text_height / 2)
+ elif anchor == ANCHOR_TOP_CENTER:
+ ref = (-text_width / 2, 0)
+ elif anchor == ANCHOR_BOTTOM_CENTER:
+ ref = (-text_width / 2, -text_height)
+ elif anchor == ANCHOR_LEFT_CENTER:
+ ref = (0, -text_height / 2)
+ elif anchor == ANCHOR_RIGHT_CENTER:
+ ref = (-text_width, -text_height / 2)
+ x += ref[0]
+ y += ref[1]
+ return x, y
diff --git a/pygtk_chart/label.pyc b/pygtk_chart/label.pyc
new file mode 100644
index 0000000..d0af0d8
--- /dev/null
+++ b/pygtk_chart/label.pyc
Binary files differ
diff --git a/pygtk_chart/multi_bar_chart.py b/pygtk_chart/multi_bar_chart.py
new file mode 100644
index 0000000..09e6a14
--- /dev/null
+++ b/pygtk_chart/multi_bar_chart.py
@@ -0,0 +1,649 @@
+# Copyright 2009 Sven Festersen <sven@sven-festersen.de>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+# MA 02110-1301, USA.
+"""
+Contains the MultiBarChart widget.
+
+Author: Sven Festersen (sven@sven-festersen.de)
+"""
+__docformat__ = "epytext"
+import cairo
+import gtk
+import gobject
+import os
+import math
+
+import pygtk_chart
+from pygtk_chart.basics import *
+from pygtk_chart import bar_chart
+from pygtk_chart.chart_object import ChartObject
+from pygtk_chart import chart
+from pygtk_chart import label
+
+MODE_VERTICAL = 0
+MODE_HORIZONTAL = 1
+
+COLOR_AUTO = 0
+COLORS = gdk_color_list_from_file(os.sep.join([os.path.dirname(__file__), "data", "tango.color"]))
+
+
+class Bar(bar_chart.Bar):
+ """
+ This is a special version of the bar_chart.Bar class that draws the
+ bars on a MultiBarChart widget.
+
+ Properties
+ ==========
+ This class inherits properties from bar_chart.Bar.
+
+ Signals
+ =======
+ This class inherits signals from bar_chart.Bar.
+ """
+
+ def __init__(self, name, value, title=""):
+ bar_chart.Bar.__init__(self, name, value, title)
+
+ #drawing methods
+ def _do_draw(self, context, rect, group, bar_count, n, i, m, j, mode, group_padding, bar_padding, maximum_value, group_end, value_label_size, label_size, label_rotation, draw_labels):
+ if mode == MODE_VERTICAL:
+ return self._do_draw_multi_vertical(context, rect, group, bar_count, n, i, m, j, mode, group_padding, bar_padding, maximum_value, group_end, value_label_size, label_size, label_rotation, draw_labels)
+ elif mode == MODE_HORIZONTAL:
+ return self._do_draw_multi_horizontal(context, rect, group, bar_count, n, i, m, j, mode, group_padding, bar_padding, maximum_value, group_end, value_label_size, label_size, label_rotation, draw_labels)
+
+ def _do_draw_multi_vertical(self, context, rect, group, bar_count, n, i, m, j, mode, group_padding, bar_padding, maximum_value, group_end, value_label_size, label_size, label_rotation, draw_labels):
+ bar_width = (rect.width - (bar_count - n) * bar_padding - (n - 1) * group_padding) / bar_count
+ bar_height = (rect.height - value_label_size - label_size) * self._value / maximum_value
+ bar_x = group_end + j * (bar_width + bar_padding)
+ bar_y = rect.y + rect.height - bar_height - label_size
+ context.set_source_rgb(*color_gdk_to_cairo(self._color))
+ bar_chart.draw_rounded_rectangle(context, bar_x, bar_y, bar_width, bar_height, self._corner_radius)
+ context.fill()
+
+ chart.add_sensitive_area(chart.AREA_RECTANGLE, (bar_x, bar_y, bar_width, bar_height), (group, self))
+
+ if self._highlighted:
+ context.set_source_rgba(1, 1, 1, 0.1)
+ bar_chart.draw_rounded_rectangle(context, bar_x, bar_y, bar_width, bar_height, self._corner_radius)
+ context.fill()
+
+ if draw_labels:
+ #draw the value label
+ self._value_label_object.set_max_width(bar_width)
+ self._value_label_object.set_text(str(self._value))
+ self._value_label_object.set_color(self._color)
+ self._value_label_object.set_position((bar_x + bar_width / 2, bar_y - 3))
+ self._value_label_object.set_anchor(label.ANCHOR_BOTTOM_CENTER)
+ self._value_label_object.draw(context, rect)
+ context.fill()
+
+ #draw label
+ self._label_object.set_rotation(label_rotation)
+ self._label_object.set_wrap(False)
+ self._label_object.set_color(self._color)
+ self._label_object.set_fixed(True)
+ self._label_object.set_max_width(3 * bar_width)
+ self._label_object.set_text(self._label)
+ self._label_object.set_position((bar_x + bar_width / 2 + 5, bar_y + bar_height + 8))
+ self._label_object.set_anchor(label.ANCHOR_TOP_RIGHT)
+ self._label_object.draw(context, rect)
+ context.fill()
+
+ return bar_x + bar_width
+
+ def _do_draw_multi_horizontal(self, context, rect, group, bar_count, n, i, m, j, mode, group_padding, bar_padding, maximum_value, group_end, value_label_size, label_size, label_rotation, draw_labels):
+ bar_height = (rect.height - (bar_count - n) * bar_padding - (n - 1) * group_padding) / bar_count
+ bar_width = (rect.width - value_label_size - label_size) * self._value / maximum_value
+ bar_x = rect.x + label_size
+ bar_y = group_end + j * (bar_height + bar_padding)
+ context.set_source_rgb(*color_gdk_to_cairo(self._color))
+ bar_chart.draw_rounded_rectangle(context, bar_x, bar_y, bar_width, bar_height, self._corner_radius)
+ context.fill()
+
+ chart.add_sensitive_area(chart.AREA_RECTANGLE, (bar_x, bar_y, bar_width, bar_height), (group, self))
+
+ if self._highlighted:
+ context.set_source_rgba(1, 1, 1, 0.1)
+ bar_chart.draw_rounded_rectangle(context, bar_x, bar_y, bar_width, bar_height, self._corner_radius)
+ context.fill()
+
+ if draw_labels:
+ #draw the value label
+ self._value_label_object.set_text(str(self._value))
+ self._value_label_object.set_wrap(False)
+ self._value_label_object.set_color(self._color)
+ self._value_label_object.set_position((bar_x + bar_width + 3, bar_y + bar_height / 2))
+ self._value_label_object.set_anchor(label.ANCHOR_LEFT_CENTER)
+ self._value_label_object.draw(context, rect)
+ context.fill()
+
+ #draw label
+ self._label_object.set_rotation(0)
+ self._label_object.set_wrap(False)
+ self._label_object.set_color(self._color)
+ self._label_object.set_fixed(True)
+ self._label_object.set_max_width(0.25 * rect.width)
+ self._label_object.set_text(self._label)
+ self._label_object.set_position((bar_x - 3, bar_y + bar_height / 2))
+ self._label_object.set_anchor(label.ANCHOR_RIGHT_CENTER)
+ self._label_object.draw(context, rect)
+ context.fill()
+
+ return bar_y + bar_height
+
+ def get_value_label_size(self, context, rect, mode, bar_count, n, group_padding, bar_padding):
+ if mode == MODE_VERTICAL:
+ bar_width = (rect.width - (bar_count - n) * bar_padding - (n - 1) * group_padding) / bar_count
+ self._value_label_object.set_max_width(bar_width)
+ self._value_label_object.set_text(str(self._value))
+ return self._value_label_object.get_calculated_dimensions(context, rect)[1]
+ elif mode == MODE_HORIZONTAL:
+ self._value_label_object.set_wrap(False)
+ self._value_label_object.set_fixed(True)
+ self._value_label_object.set_text(str(self._value))
+ return self._value_label_object.get_calculated_dimensions(context, rect)[0]
+
+ def get_label_size(self, context, rect, mode, bar_count, n, group_padding, bar_padding, label_rotation):
+ if mode == MODE_VERTICAL:
+ bar_width = (rect.width - (bar_count - n) * bar_padding - (n - 1) * group_padding) / bar_count
+ self._label_object.set_rotation(label_rotation)
+ self._label_object.set_wrap(False)
+ self._label_object.set_fixed(True)
+ self._label_object.set_max_width(3 * bar_width)
+ self._label_object.set_text(self._label)
+ return self._label_object.get_calculated_dimensions(context, rect)[1]
+ elif mode == MODE_HORIZONTAL:
+ self._label_object.set_max_width(0.25 * rect.width)
+ self._label_object.set_text(self._label)
+ return self._label_object.get_calculated_dimensions(context, rect)[0]
+
+
+
+class BarGroup(ChartObject):
+ """
+ This class represents a group of bars on the MultiBarChart widget.
+
+ Properties
+ ==========
+ This class has the following properties:
+ - name (a unique identifier for the group, type: string)
+ - title (a title for the group, type: string)
+ - bar-padding (the space between two bars of the group in px,
+ type: int in [0, 100])
+ - bars (a list of the bars in the group, read only)
+ - maximum-value (the maximum value of the bars in the group, read
+ only)
+ - bar-count (the number of bars in the group, read only).
+
+ Signals
+ =======
+ The BarGroup class inherits signals from chart_object.ChartObject.
+ """
+
+ __gproperties__ = {"name": (gobject.TYPE_STRING, "group name",
+ "A unique identifier for this group.",
+ "", gobject.PARAM_READABLE),
+ "title": (gobject.TYPE_STRING, "group title",
+ "The group's title.", "",
+ gobject.PARAM_READWRITE),
+ "bar-padding": (gobject.TYPE_INT, "bar padding",
+ "The space between bars in this group.",
+ 0, 100, 2, gobject.PARAM_READWRITE),
+ "bars": (gobject.TYPE_PYOBJECT, "bars in the group",
+ "A list of bars in this group.",
+ gobject.PARAM_READABLE),
+ "maximum-value": (gobject.TYPE_FLOAT, "max value",
+ "The maximum value of the bars in this group.",
+ 0, 9999999, 0, gobject.PARAM_READABLE),
+ "bar-count": (gobject.TYPE_INT, "bar count",
+ "The number of bars in this group.",
+ 0, 100, 0, gobject.PARAM_READWRITE)}
+
+ def __init__(self, name, title=""):
+ ChartObject.__init__(self)
+ #private properties:
+ self._group_label_object = label.Label((0, 0), title)
+ #gobject properties:
+ self._bars = []
+ self._name = name
+ self._title = title
+ self._bar_padding = 2
+
+ #gobject set_* and get_* methods
+ def do_get_property(self, property):
+ if property.name == "visible":
+ return self._show
+ elif property.name == "antialias":
+ return self._antialias
+ elif property.name == "name":
+ return self._name
+ elif property.name == "title":
+ return self._title
+ elif property.name == "bar-padding":
+ return self._bar_padding
+ elif property.name == "bars":
+ return self._bars
+ elif property.name == "maximum-value":
+ return max(bar.get_value() for bar in self._bars)
+ elif property.name == "bar-count":
+ return len(self._bars)
+ else:
+ raise AttributeError, "Property %s does not exist." % property.name
+
+ def do_set_property(self, property, value):
+ if property.name == "visible":
+ self._show = value
+ elif property.name == "antialias":
+ self._antialias = value
+ elif property.name == "name":
+ self._name = value
+ elif property.name == "title":
+ self._title = value
+ elif property.name == "bar-padding":
+ self._bar_padding = value
+ else:
+ raise AttributeError, "Property %s does not exist." % property.name
+
+ def get_bar_count(self):
+ """
+ Returns the number of bars in this group.
+
+ @return: int in [0, 100].
+ """
+ return self.get_property("bar-count")
+
+ def get_maximum_value(self):
+ """
+ Returns the maximum value of the bars in this group.
+
+ @return: float.
+ """
+ return self.get_property("maximum-value")
+
+ def get_bars(self):
+ """
+ Returns a list of the bars in this group.
+
+ @return: list of multi_bar_chart.Bar.
+ """
+ return self.get_property("bars")
+
+ def get_name(self):
+ """
+ Returns the name (a unique identifier) of this group.
+
+ @return: string.
+ """
+ return self.get_property("name")
+
+ def set_title(self, title):
+ """
+ Set the title of the group.
+
+ @param title: the new title
+ @type title: string.
+ """
+ self.set_property("title", title)
+ self.emit("appearance_changed")
+
+ def get_title(self):
+ """
+ Returns the title of the group.
+
+ @return: string.
+ """
+ return self.get_property("title")
+
+ def get_label(self):
+ """
+ Alias for get_title.
+
+ @return: string.
+ """
+ return self.get_title()
+
+ def set_bar_padding(self, padding):
+ """
+ Set the distance between two bars in this group (in px).
+
+ @param padding: the padding in px
+ @type padding: int in [0, 100].
+ """
+ self.set_property("bar-padding", padding)
+ self.emit("appearance_changed")
+
+ def get_bar_padding(self):
+ """
+ Returns the distance of two bars in the group (in px).
+
+ @return: int in [0, 100].
+ """
+ return self.get_property("bar-padding")
+
+ #drawing methods
+ def _do_draw(self, context, rect, bar_count, n, i, mode, group_padding, maximum_value, group_end, value_label_size, label_size, label_rotation, draw_labels, rotate_label_horizontal):
+ end = group_end
+ for j, bar in enumerate(self._bars):
+ end = bar.draw(context, rect, self, bar_count, n, i, len(self._bars), j, mode, group_padding, self._bar_padding, maximum_value, group_end, value_label_size, label_size, label_rotation, draw_labels)
+
+ if draw_labels and mode == MODE_VERTICAL:
+ context.set_source_rgb(0, 0, 0)
+ group_width = end - group_end
+ self._group_label_object.set_text(self._title)
+ self._group_label_object.set_fixed(True)
+ self._group_label_object.set_max_width(group_width)
+ self._group_label_object.set_position((group_end + group_width / 2, rect.y + rect.height))
+ self._group_label_object.set_anchor(label.ANCHOR_BOTTOM_CENTER)
+ self._group_label_object.draw(context, rect)
+ context.fill()
+ elif draw_labels and mode == MODE_HORIZONTAL:
+ context.set_source_rgb(0, 0, 0)
+ group_height = end - group_end
+ if rotate_label_horizontal:
+ self._group_label_object.set_rotation(90)
+ offset = self.get_group_label_size(context, rect, mode, rotate_label_horizontal) #fixes postioning bug
+ else:
+ self._group_label_object.set_rotation(0)
+ offset = 0
+ self._group_label_object.set_text(self._title)
+ self._group_label_object.set_wrap(False)
+ self._group_label_object.set_fixed(True)
+ self._group_label_object.set_position((rect.x + offset, group_end + group_height / 2))
+ self._group_label_object.set_anchor(label.ANCHOR_LEFT_CENTER)
+ self._group_label_object.draw(context, rect)
+ context.fill()
+
+ return end + group_padding
+
+ #other methods
+ def add_bar(self, bar):
+ """
+ Add a bar to the group.
+
+ @param bar: the bar to add
+ @type bar: multi_bar_chart.Bar.
+ """
+ if bar.get_color() == COLOR_AUTO:
+ bar.set_color(COLORS[len(self._bars) % len(COLORS)])
+ self._bars.append(bar)
+ self.emit("appearance_changed")
+
+ def get_value_label_size(self, context, rect, mode, bar_count, n, group_padding, bar_padding):
+ value_label_size = 0
+ for bar in self._bars:
+ value_label_size = max(value_label_size, bar.get_value_label_size(context, rect, mode, bar_count, n, group_padding, bar_padding))
+ return value_label_size
+
+ def get_label_size(self, context, rect, mode, bar_count, n, group_padding, bar_padding, label_rotation):
+ label_size = 0
+ for bar in self._bars:
+ label_size = max(label_size, bar.get_label_size(context, rect, mode, bar_count, n, group_padding, bar_padding, label_rotation))
+ return label_size
+
+ def get_group_label_size(self, context, rect, mode, rotate_label_horizontal):
+ self._group_label_object.set_text(self._title)
+ if mode == MODE_VERTICAL:
+ return self._group_label_object.get_calculated_dimensions(context, rect)[1]
+ elif mode == MODE_HORIZONTAL:
+ if rotate_label_horizontal:
+ self._group_label_object.set_rotation(90)
+ else:
+ self._group_label_object.set_rotation(0)
+ self._group_label_object.set_wrap(False)
+ return self._group_label_object.get_calculated_dimensions(context, rect)[0]
+
+
+class MultiBarChart(bar_chart.BarChart):
+ """
+ The MultiBarChart widget displays groups of bars.
+ Usage: create multi_bar_chart.BarGroups and
+ add multi_bar_chart.Bars. The add the bar groups to MultiBarChart.
+
+ Properties
+ ==========
+ The MultiBarChart class inherits properties from bar_chart.BarChart
+ (except bar-padding). Additional properties:
+ - group-padding (the space between two bar groups in px, type: int
+ in [0, 100], default: 16)
+ - label-rotation (the angle (in degrees) that should be used to
+ rotate bar labels in vertical mode, type: int in [0, 360],
+ default: 300)
+ - rotate-group-labels (sets whether group labels should be roteated
+ by 90 degrees in horizontal mode, type: boolean, default: False).
+
+ Signals
+ =======
+ The MultiBarChart class inherits the signal 'bar-clicked' from
+ bar_chart.BarChart. Additional signals:
+ - group-clicked: emitted when a bar is clicked, callback signature:
+ def group_clicked(chart, group, bar).
+ """
+
+ __gsignals__ = {"group-clicked": (gobject.SIGNAL_RUN_LAST,
+ gobject.TYPE_NONE,
+ (gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT))}
+
+ __gproperties__ = {"group-padding": (gobject.TYPE_INT, "group padding",
+ "The space between two bar groups.",
+ 0, 100, 16, gobject.PARAM_READWRITE),
+ "label-rotation": (gobject.TYPE_INT, "label rotation",
+ "The angle that should bar labels be rotated by in vertical mode.",
+ 0, 360, 300, gobject.PARAM_READWRITE),
+ "rotate-group-labels": (gobject.TYPE_BOOLEAN,
+ "rotate group label",
+ "Sets whether the group label should be rotated by 90 degrees in horizontal mode.",
+ False, gobject.PARAM_READWRITE),
+ "mode": (gobject.TYPE_INT, "mode",
+ "The chart's mode.", 0, 1, 0,
+ gobject.PARAM_READWRITE),
+ "draw-labels": (gobject.TYPE_BOOLEAN,
+ "draw labels", "Set whether to draw labels on bars.",
+ True, gobject.PARAM_READWRITE),
+ "enable-mouseover": (gobject.TYPE_BOOLEAN, "enable mouseover",
+ "Set whether to enable mouseover effect.",
+ True, gobject.PARAM_READWRITE)}
+
+ def __init__(self):
+ bar_chart.BarChart.__init__(self)
+ #private properties:
+ self._groups = []
+ #gobject properties:
+ self._group_padding = 16
+ self._label_rotation = 300
+ self._rotate_group_label_in_horizontal_mode = False
+
+ #gobject set_* and get_* methods
+ def do_get_property(self, property):
+ if property.name == "group-padding":
+ return self._group_padding
+ elif property.name == "label-rotation":
+ return self._label_rotation
+ elif property.name == "rotate-group-labels":
+ return self._rotate_group_label_in_horizontal_mode
+ elif property.name == "mode":
+ return self._mode
+ elif property.name == "draw-labels":
+ return self._draw_labels
+ elif property.name == "enable-mouseover":
+ return self._mouseover
+ else:
+ raise AttributeError, "Property %s does not exist." % property.name
+
+ def do_set_property(self, property, value):
+ if property.name == "group-padding":
+ self._group_padding = value
+ elif property.name == "label-rotation":
+ self._label_rotation = value
+ elif property.name == "rotate-group-labels":
+ self._rotate_group_label_in_horizontal_mode = value
+ elif property.name == "mode":
+ self._mode = value
+ elif property.name == "draw-labels":
+ self._draw_labels = value
+ elif property.name == "enable-mouseover":
+ self._mouseover = value
+ else:
+ raise AttributeError, "Property %s does not exist." % property.name
+
+ def set_group_padding(self, padding):
+ """
+ Set the amount of free space between bar groups (in px,
+ default: 16).
+
+ @param padding: the padding
+ @type padding: int in [0, 100].
+ """
+ self.set_property("group-padding", padding)
+ self.queue_draw()
+
+ def get_group_padding(self):
+ """
+ Returns the amount of free space between two bar groups (in px).
+
+ @return: int in [0, 100].
+ """
+ return self.get_property("group-padding")
+
+ def set_label_rotation(self, angle):
+ """
+ Set the abgle (in degrees) that should be used to rotate the
+ bar labels in vertical mode (defualt: 300 degrees).
+
+ @type angle: int in [0, 360].
+ """
+ self.set_property("label-rotation", angle)
+ self.queue_draw()
+
+ def get_label_rotation(self):
+ """
+ Returns the angle by which bar labels are rotated in vertical
+ mode.
+
+ @return: int in [0, 350].
+ """
+ return self.get_property("label-rotation")
+
+ def set_rotate_group_labels(self, rotate):
+ """
+ Set wether the groups' labels should be rotated by 90 degrees in
+ horizontal mode (default: False).
+
+ @type rotate: boolean.
+ """
+ self.set_property("rotate-group-labels", rotate)
+ self.queue_draw()
+
+ def get_rotate_group_labels(self):
+ """
+ Returns True if group labels should be rotated by 90 degrees
+ in horizontal mode.
+
+ @return: boolean.
+ """
+ return self.get_property("rotate-group-labels")
+
+ #callbacks
+ def _cb_motion_notify(self, widget, event):
+ if not self._mouseover: return
+ active = chart.get_sensitive_areas(event.x, event.y)
+ if active == []: return
+ for group in self._groups:
+ for bar in group.get_bars():
+ bar.set_highlighted((group, bar) in active)
+ self.queue_draw()
+
+ def _cb_button_pressed(self, widget, event):
+ active = chart.get_sensitive_areas(event.x, event.y)
+ for group, bar in active:
+ self.emit("group-clicked", group, bar)
+ self.emit("bar-clicked", bar)
+
+ #drawing methods
+ def _do_draw_groups(self, context, rect, maximum_value, value_label_size, label_size, bar_count):
+ if self._groups == []: return
+
+ if self._mode == MODE_VERTICAL:
+ group_end = rect.x
+ else:
+ group_end = rect.y
+
+ for i, group in enumerate(self._groups):
+ group_end = group.draw(context, rect, bar_count, len(self._groups), i, self._mode, self._group_padding, maximum_value, group_end, value_label_size, label_size, self._label_rotation, self._draw_labels, self._rotate_group_label_in_horizontal_mode)
+
+ def draw(self, context):
+ """
+ Draw the widget. This method is called automatically. Don't call it
+ yourself. If you want to force a redrawing of the widget, call
+ the queue_draw() method.
+
+ @type context: cairo.Context
+ @param context: The context to draw on.
+ """
+ label.begin_drawing()
+ chart.init_sensitive_areas()
+
+ rect = self.get_allocation()
+ rect = gtk.gdk.Rectangle(0, 0, rect.width, rect.height) #transform rect to context coordinates
+ context.set_line_width(1)
+
+ rect = self.draw_basics(context, rect)
+
+ maximum_value = max(group.get_maximum_value() for group in self._groups)
+ bar_count = 0
+ for group in self._groups: bar_count += group.get_bar_count()
+
+ value_label_size = 0
+ if self._draw_labels:
+ for group in self._groups:
+ value_label_size = max(value_label_size, group.get_value_label_size(context, rect, self._mode, bar_count, len(self._groups), self._group_padding, self._bar_padding))
+
+ label_size = 0
+ if self._draw_labels:
+ for group in self._groups:
+ label_size = max(label_size, group.get_label_size(context, rect, self._mode, bar_count, len(self._groups), self._group_padding, self._bar_padding, self._label_rotation))
+ label_size += 10
+ label_size += group.get_group_label_size(context, rect, self._mode, self._rotate_group_label_in_horizontal_mode)
+
+ rect = self._do_draw_grid(context, rect, maximum_value, value_label_size, label_size)
+ self._do_draw_groups(context, rect, maximum_value, value_label_size, label_size, bar_count)
+
+ label.finish_drawing()
+ n = len(self._groups)
+ if self._mode == MODE_VERTICAL:
+ minimum_width = rect.x + self._padding + bar_count * 10 + n * self._group_padding
+ minimum_height = rect.y + self._padding + 200
+ elif self._mode == MODE_HORIZONTAL:
+ minimum_width = rect.x + self._padding + 200
+ minimum_height = rect.y + self._padding + bar_count * 10 + n * self._group_padding
+ self.set_size_request(minimum_width, minimum_height)
+
+ #other methods
+ def add_group(self, group):
+ """
+ Add a BarGroup to the chart.
+
+ @type group: multi_bar_chart.BarGroup.
+ """
+ self._groups.append(group)
+ self.queue_draw()
+
+ def add_bar(self, bar):
+ """
+ Alias for add_group.
+ This method is deprecated. Use add_group instead.
+ """
+ print "MultiBarChart.add_bar is deprecated. Use add_group instead."
+ self.add_group(bar)
diff --git a/pygtk_chart/multi_bar_chart.pyc b/pygtk_chart/multi_bar_chart.pyc
new file mode 100644
index 0000000..bceafe1
--- /dev/null
+++ b/pygtk_chart/multi_bar_chart.pyc
Binary files differ
diff --git a/pygtk_chart/pie_chart.py b/pygtk_chart/pie_chart.py
new file mode 100644
index 0000000..3e3871d
--- /dev/null
+++ b/pygtk_chart/pie_chart.py
@@ -0,0 +1,474 @@
+#!/usr/bin/env python
+#
+# pie_chart.py
+#
+# Copyright 2008 Sven Festersen <sven@sven-festersen.de>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+# MA 02110-1301, USA.
+"""
+Contains the PieChart widget.
+
+Author: Sven Festersen (sven@sven-festersen.de)
+"""
+__docformat__ = "epytext"
+import cairo
+import gobject
+import gtk
+import math
+import os
+
+from pygtk_chart.basics import *
+from pygtk_chart.chart_object import ChartObject
+from pygtk_chart import chart
+from pygtk_chart import label
+from pygtk_chart import COLORS, COLOR_AUTO
+
+def draw_sector(context, cx, cy, radius, angle, angle_offset):
+ context.move_to(cx, cy)
+ context.arc(cx, cy, radius, angle_offset, angle_offset + angle)
+ context.close_path()
+ context.fill()
+
+
+class PieArea(chart.Area):
+ """
+ This class represents the sector of a pie chart.
+
+ Properties
+ ==========
+ The PieArea class inherits properties from chart.Area.
+
+ Signals
+ =======
+ The PieArea class inherits signals from chart.Area.
+ """
+
+ def __init__(self, name, value, title=""):
+ chart.Area.__init__(self, name, value, title)
+ self._label_object = label.Label((0, 0), title)
+
+ def _do_draw(self, context, rect, cx, cy, radius, angle, angle_offset, draw_label, draw_percentage, draw_value):
+ context.set_source_rgb(*color_gdk_to_cairo(self._color))
+ draw_sector(context, cx, cy, radius, angle, angle_offset)
+ if self._highlighted:
+ context.set_source_rgba(1, 1, 1, 0.1)
+ draw_sector(context, cx, cy, radius, angle, angle_offset)
+
+ if draw_label:
+ title = self._label
+ title_extra = ""
+ fraction = angle / (2 * math.pi)
+ if draw_percentage and not draw_value:
+ title_extra = " (%s%%)" % round(100 * fraction, 2)
+ elif not draw_percentage and draw_value:
+ title_extra = " (%s)" % self._value
+ elif draw_percentage and draw_value:
+ title_extra = " (%s, %s%%)" % (self._value, round(100 * fraction, 2))
+ title += title_extra
+
+ label_angle = angle_offset + angle / 2
+ label_angle = label_angle % (2 * math.pi)
+ x = cx + (radius + 10) * math.cos(label_angle)
+ y = cy + (radius + 10) * math.sin(label_angle)
+
+ ref = label.ANCHOR_BOTTOM_LEFT
+ if 0 <= label_angle <= math.pi / 2:
+ ref = label.ANCHOR_TOP_LEFT
+ elif math.pi / 2 <= label_angle <= math.pi:
+ ref = label.ANCHOR_TOP_RIGHT
+ elif math.pi <= label_angle <= 1.5 * math.pi:
+ ref = label.ANCHOR_BOTTOM_RIGHT
+
+ if self._highlighted:
+ self._label_object.set_underline(label.UNDERLINE_SINGLE)
+ else:
+ self._label_object.set_underline(label.UNDERLINE_NONE)
+ self._label_object.set_color(self._color)
+ self._label_object.set_text(title)
+ self._label_object.set_position((x, y))
+ self._label_object.set_anchor(ref)
+ self._label_object.draw(context, rect)
+
+
+class PieChart(chart.Chart):
+ """
+ This is the pie chart class.
+
+ Properties
+ ==========
+ The PieChart class inherits properties from chart.Chart.
+ Additional properties:
+ - rotate (the angle that the pie chart should be rotated by in
+ degrees, type: int in [0, 360])
+ - draw-shadow (sets whther to draw a shadow under the pie chart,
+ type: boolean)
+ - draw-labels (sets whether to draw area labels, type: boolean)
+ - show-percentage (sets whether to show percentage in area labels,
+ type: boolean)
+ - show-values (sets whether to show values in area labels,
+ type: boolean)
+ - enable-scroll (sets whether the pie chart can be rotated by
+ scrolling with the mouse wheel, type: boolean)
+ - enable-mouseover (sets whether a mouse over effect should be
+ added to areas, type: boolean).
+
+ Signals
+ =======
+ The PieChart class inherits signals from chart.Chart.
+ Additional signals:
+ - area-clicked (emitted when an area is clicked)
+ callback signature:
+ def callback(piechart, area).
+ """
+
+ __gproperties__ = {"rotate": (gobject.TYPE_INT,
+ "rotation",
+ "The angle to rotate the chart in degrees.",
+ 0, 360, 0, gobject.PARAM_READWRITE),
+ "draw-shadow": (gobject.TYPE_BOOLEAN,
+ "draw pie shadow",
+ "Set whether to draw pie shadow.",
+ True, gobject.PARAM_READWRITE),
+ "draw-labels": (gobject.TYPE_BOOLEAN,
+ "draw area labels",
+ "Set whether to draw area labels.",
+ True, gobject.PARAM_READWRITE),
+ "show-percentage": (gobject.TYPE_BOOLEAN,
+ "show percentage",
+ "Set whether to show percentage in the areas' labels.",
+ False, gobject.PARAM_READWRITE),
+ "show-values": (gobject.TYPE_BOOLEAN,
+ "show values",
+ "Set whether to show values in the areas' labels.",
+ True, gobject.PARAM_READWRITE),
+ "enable-scroll": (gobject.TYPE_BOOLEAN,
+ "enable scroll",
+ "If True, the pie can be rotated by scrolling with the mouse wheel.",
+ True, gobject.PARAM_READWRITE),
+ "enable-mouseover": (gobject.TYPE_BOOLEAN,
+ "enable mouseover",
+ "Set whether a mouseover effect should be visible if moving the mouse over a pie area.",
+ True, gobject.PARAM_READWRITE)}
+
+ __gsignals__ = {"area-clicked": (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT,))}
+
+ def __init__(self):
+ chart.Chart.__init__(self)
+ self._areas = []
+ self._rotate = 0
+ self._shadow = True
+ self._labels = True
+ self._percentage = False
+ self._values = True
+ self._enable_scroll = True
+ self._enable_mouseover = True
+
+ self.add_events(gtk.gdk.BUTTON_PRESS_MASK|gtk.gdk.SCROLL_MASK|gtk.gdk.POINTER_MOTION_MASK)
+ self.connect("button_press_event", self._cb_button_pressed)
+ self.connect("scroll-event", self._cb_scroll_event)
+ self.connect("motion-notify-event", self._cb_motion_notify)
+
+ def do_get_property(self, property):
+ if property.name == "rotate":
+ return self._rotate
+ elif property.name == "draw-shadow":
+ return self._shadow
+ elif property.name == "draw-labels":
+ return self._labels
+ elif property.name == "show-percentage":
+ return self._percentage
+ elif property.name == "show-values":
+ return self._values
+ elif property.name == "enable-scroll":
+ return self._enable_scroll
+ elif property.name == "enable-mouseover":
+ return self._enable_mouseover
+ else:
+ raise AttributeError, "Property %s does not exist." % property.name
+
+ def do_set_property(self, property, value):
+ if property.name == "rotate":
+ self._rotate = value
+ elif property.name == "draw-shadow":
+ self._shadow = value
+ elif property.name == "draw-labels":
+ self._labels = value
+ elif property.name == "show-percentage":
+ self._percentage = value
+ elif property.name == "show-values":
+ self._values = value
+ elif property.name == "enable-scroll":
+ self._enable_scroll = value
+ elif property.name == "enable-mouseover":
+ self._enable_mouseover = value
+ else:
+ raise AttributeError, "Property %s does not exist." % property.name
+
+ def _cb_appearance_changed(self, widget):
+ self.queue_draw()
+
+ def _cb_motion_notify(self, widget, event):
+ if not self._enable_mouseover: return
+ area = self._get_area_at_pos(event.x, event.y)
+ for a in self._areas:
+ a.set_property("highlighted", a == area)
+ self.queue_draw()
+
+ def _cb_button_pressed(self, widget, event):
+ area = self._get_area_at_pos(event.x, event.y)
+ if area:
+ self.emit("area-clicked", area)
+
+ def _get_area_at_pos(self, x, y):
+ rect = self.get_allocation()
+ center = rect.width / 2, rect.height / 2
+ x = x - center[0]
+ y = y - center[1]
+
+ #calculate angle
+ angle = math.atan2(x, -y)
+ angle -= math.pi / 2
+ angle -= 2 * math.pi * self.get_rotate() / 360.0
+ while angle < 0:
+ angle += 2 * math.pi
+
+ #calculate radius
+ radius_squared = math.pow(int(0.4 * min(rect.width, rect.height)), 2)
+ clicked_radius_squared = x*x + y*y
+
+ if clicked_radius_squared <= radius_squared:
+ #find out area that was clicked
+ sum = 0
+ for area in self._areas:
+ if area.get_visible():
+ sum += area.get_value()
+
+ current_angle_position = 0
+ for area in self._areas:
+ area_angle = 2 * math.pi * area.get_value() / sum
+
+ if current_angle_position <= angle <= current_angle_position + area_angle:
+ return area
+
+ current_angle_position += area_angle
+ return None
+
+ def _cb_scroll_event(self, widget, event):
+ if not self._enable_scroll: return
+ if event.direction in [gtk.gdk.SCROLL_UP, gtk.gdk.SCROLL_RIGHT]:
+ delta = 360.0 / 32
+ elif event.direction in [gtk.gdk.SCROLL_DOWN, gtk.gdk.SCROLL_LEFT]:
+ delta = - 360.0 / 32
+ else:
+ delta = 0
+ rotate = self.get_rotate() + delta
+ rotate = rotate % 360.0
+ if rotate < 0: rotate += 360
+ self.set_rotate(rotate)
+
+ def draw(self, context):
+ """
+ Draw the widget. This method is called automatically. Don't call it
+ yourself. If you want to force a redrawing of the widget, call
+ the queue_draw() method.
+
+ @type context: cairo.Context
+ @param context: The context to draw on.
+ """
+ label.begin_drawing()
+
+ rect = self.get_allocation()
+ #initial context settings: line width & font
+ context.set_line_width(1)
+ font = gtk.Label().style.font_desc.get_family()
+ context.select_font_face(font, cairo.FONT_SLANT_NORMAL, \
+ cairo.FONT_WEIGHT_NORMAL)
+
+ self.draw_basics(context, rect)
+ self._do_draw_shadow(context, rect)
+ self._do_draw_areas(context, rect)
+
+ label.finish_drawing()
+
+ def _do_draw_areas(self, context, rect):
+ center = rect.width / 2, rect.height / 2
+ radius = int(0.4 * min(rect.width, rect.height))
+ sum = 0
+
+ for area in self._areas:
+ if area.get_visible():
+ sum += area.get_value()
+
+ current_angle_position = 2 * math.pi * self.get_rotate() / 360.0
+ for i, area in enumerate(self._areas):
+ area_angle = 2 * math.pi * area.get_value() / sum
+ area.draw(context, rect, center[0], center[1], radius, area_angle, current_angle_position, self._labels, self._percentage, self._values)
+ current_angle_position += area_angle
+
+ def _do_draw_shadow(self, context, rect):
+ if not self._shadow: return
+ center = rect.width / 2, rect.height / 2
+ radius = int(0.4 * min(rect.width, rect.height))
+
+ gradient = cairo.RadialGradient(center[0], center[1], radius, center[0], center[1], radius + 10)
+ gradient.add_color_stop_rgba(0, 0, 0, 0, 0.5)
+ gradient.add_color_stop_rgba(0.5, 0, 0, 0, 0)
+
+ context.set_source(gradient)
+ context.arc(center[0], center[1], radius + 10, 0, 2 * math.pi)
+ context.fill()
+
+ def add_area(self, area):
+ color = area.get_color()
+ if color == COLOR_AUTO: area.set_color(COLORS[len(self._areas) % len(COLORS)])
+ self._areas.append(area)
+ area.connect("appearance_changed", self._cb_appearance_changed)
+
+ def get_pie_area(self, name):
+ """
+ Returns the PieArea with the id 'name' if it exists, None
+ otherwise.
+
+ @type name: string
+ @param name: the id of a PieArea
+
+ @return: a PieArea or None.
+ """
+ for area in self._areas:
+ if area.get_name() == name:
+ return area
+ return None
+
+ def set_rotate(self, angle):
+ """
+ Set the rotation angle of the PieChart in degrees.
+
+ @param angle: angle in degrees 0 - 360
+ @type angle: integer.
+ """
+ self.set_property("rotate", angle)
+ self.queue_draw()
+
+ def get_rotate(self):
+ """
+ Get the current rotation angle in degrees.
+
+ @return: integer.
+ """
+ return self.get_property("rotate")
+
+ def set_draw_shadow(self, draw):
+ """
+ Set whether to draw the pie chart's shadow.
+
+ @type draw: boolean.
+ """
+ self.set_property("draw-shadow", draw)
+ self.queue_draw()
+
+ def get_draw_shadow(self):
+ """
+ Returns True if pie chart currently has a shadow.
+
+ @return: boolean.
+ """
+ return self.get_property("draw-shadow")
+
+ def set_draw_labels(self, draw):
+ """
+ Set whether to draw the labels of the pie areas.
+
+ @type draw: boolean.
+ """
+ self.set_property("draw-labels", draw)
+ self.queue_draw()
+
+ def get_draw_labels(self):
+ """
+ Returns True if area labels are shown.
+
+ @return: boolean.
+ """
+ return self.get_property("draw-labels")
+
+ def set_show_percentage(self, show):
+ """
+ Set whether to show the percentage an area has in its label.
+
+ @type show: boolean.
+ """
+ self.set_property("show-percentage", show)
+ self.queue_draw()
+
+ def get_show_percentage(self):
+ """
+ Returns True if percentages are shown.
+
+ @return: boolean.
+ """
+ return self.get_property("show-percentage")
+
+ def set_enable_scroll(self, scroll):
+ """
+ Set whether the pie chart can be rotated by scrolling with
+ the mouse wheel.
+
+ @type scroll: boolean.
+ """
+ self.set_property("enable-scroll", scroll)
+
+ def get_enable_scroll(self):
+ """
+ Returns True if the user can rotate the pie chart by scrolling.
+
+ @return: boolean.
+ """
+ return self.get_property("enable-scroll")
+
+ def set_enable_mouseover(self, mouseover):
+ """
+ Set whether a mouseover effect should be shown when the pointer
+ enters a pie area.
+
+ @type mouseover: boolean.
+ """
+ self.set_property("enable-mouseover", mouseover)
+
+ def get_enable_mouseover(self):
+ """
+ Returns True if the mouseover effect is enabled.
+
+ @return: boolean.
+ """
+ return self.get_property("enable-mouseover")
+
+ def set_show_values(self, show):
+ """
+ Set whether the area's value should be shown in its label.
+
+ @type show: boolean.
+ """
+ self.set_property("show-values", show)
+ self.queue_draw()
+
+ def get_show_values(self):
+ """
+ Returns True if the value of a pie area is shown in its label.
+
+ @return: boolean.
+ """
+ return self.get_property("show-values")
+
diff --git a/setup.py b/setup.py
new file mode 100755
index 0000000..ec0f64e
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,3 @@
+#!/usr/bin/env python
+from sugar.activity import bundlebuilder
+bundlebuilder.start()