Compare commits
228 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0e6ed92c53 | |||
| 56c77ae9a4 | |||
| c46d29a5bb | |||
| 5e8c33f05a | |||
| fb920f58bc | |||
| 292d290723 | |||
| 0adcdaba8d | |||
| c2926c96d6 | |||
| 0e22fead19 | |||
| 920ad915d1 | |||
| 92016fe5c1 | |||
| e9edb602e3 | |||
| 301161926f | |||
| e7b0288fc5 | |||
| d04f1df252 | |||
| dba6f46200 | |||
| c1f4c0534c | |||
| 9bf8eb506f | |||
| 618d57091b | |||
| ef291c3ae6 | |||
| f44f80874d | |||
| 400cc5ab4f | |||
| 453fb848aa | |||
| e51a447809 | |||
| ea667cf03e | |||
| 6709576aec | |||
| ccc6b67dee | |||
| e0a8753c13 | |||
| 2729f660e9 | |||
| 49bbcd8d53 | |||
| 42e29b36b2 | |||
| e5edc7bb7f | |||
| 73754a2222 | |||
| 0eac79112f | |||
| de6253dad9 | |||
| 29f1266f8d | |||
| 3b95c6a16f | |||
| be69067396 | |||
| 1eaad783f9 | |||
| d0b23f9202 | |||
| 0038a5331d | |||
| 1a4bbbb653 | |||
| 9f03954433 | |||
| 697bd8124c | |||
| 1b0d76e6ba | |||
| 800f30fc4a | |||
| 20563ece6d | |||
| 33b5e76f99 | |||
| aea871b30e | |||
| 574767de2a | |||
| 1684065b6a | |||
| a9efa754d8 | |||
| 0e5b5037f9 | |||
| 6ee0520823 | |||
| 2cc803ddae | |||
| 0e9f1c5503 | |||
| 8080ccc7f0 | |||
| e774353a8b | |||
| 8a929adcdd | |||
| f6cdb9fb08 | |||
| 25da838972 | |||
| a98ba36a23 | |||
| f98c905591 | |||
| 6691415f12 | |||
| da2df60c40 | |||
| da2f9cc7a7 | |||
| 6dac5f887c | |||
| 4a5b1d433f | |||
| db599beae3 | |||
| 81d37ba053 | |||
| 59db470ffa | |||
| 3fd3b2ec19 | |||
| 371f702f9d | |||
| 83f76552e2 | |||
| 2b27b06c24 | |||
| 66694f05c2 | |||
| b41df8d2d7 | |||
| 978145e089 | |||
| 4a4730a5bb | |||
| 55c5e981b5 | |||
| afd9c34760 | |||
| 2d010620d4 | |||
| e3069a3e12 | |||
| 6de1f88d7d | |||
| 6926a93223 | |||
| d937ecbaff | |||
| 0f371c7fd2 | |||
| 5ffbf9a15a | |||
| a36ca62b73 | |||
| 344f4be232 | |||
| c6d2ea795a | |||
| 14c23170ba | |||
| f40106ef3e | |||
| be230a4e9f | |||
| efda4661bc | |||
| c72639af8e | |||
| 0279279083 | |||
| fe5bb49fa4 | |||
| 922ec99990 | |||
| bc4f329d15 | |||
| 2a1cb3c1ba | |||
| 07857c060d | |||
| 347b8bca4c | |||
| bdd8181d8d | |||
| a0b7264424 | |||
| be20420f74 | |||
| 8fe3893cdd | |||
| 4dcfaa51e7 | |||
| 15cbe23161 | |||
| 624f42f685 | |||
| f389ddc074 | |||
| 126360c3ef | |||
| 851321327a | |||
| e33c809825 | |||
| 363e5289b1 | |||
| a08503cb02 | |||
| f978b75c7c | |||
| 4e3de66f89 | |||
| 1af0df2105 | |||
| cfc22c2a42 | |||
| 96ceebbf88 | |||
| 5c1da0f9c0 | |||
| 3282000e03 | |||
| c779b83259 | |||
| fa10153d13 | |||
| 1b04745ef8 | |||
| 0ef11257ac | |||
| 8a10e49a84 | |||
| f42e134236 | |||
| 0b9c154aab | |||
| b064624c4b | |||
| 3de5c1f8cd | |||
| 2eab5ff145 | |||
| b01223bec0 | |||
| ab3d0ea172 | |||
| 60fb364f3f | |||
| a00a60a8dd | |||
| e04e77a527 | |||
| be3e4032c7 | |||
| c68c108a36 | |||
| 610733ca33 | |||
| 56e11e21ab | |||
| a9897caf79 | |||
| a48458d1f9 | |||
| a6019fff41 | |||
| df9ea38a03 | |||
| 4edcc521cd | |||
| 70672ec83c | |||
| 653f51da15 | |||
| 1e70c4f69f | |||
| eac73cd58b | |||
| fb21b274e0 | |||
| 9f512ec834 | |||
| afc1e325b0 | |||
| c54ca0f0bf | |||
| dc54cebd12 | |||
| 3b2635d6e2 | |||
| da52c1aa03 | |||
| e331bdbfc2 | |||
| 3bc24ab9e8 | |||
| 871a3d9c3e | |||
| cc151f00ca | |||
| 386148e356 | |||
| baa5f3af6e | |||
| 9441ee64c5 | |||
| 793716c66d | |||
| 98ef8ee6c7 | |||
| 7e33eb55f5 | |||
| f695f82c1f | |||
| 45859e1a3d | |||
| e3fa65da45 | |||
| 1ac3caadce | |||
| 3ce3c7445a | |||
| ae4019ad19 | |||
| 200c556ce9 | |||
| c8dbd803f3 | |||
| e3a08713e9 | |||
| c6510bea51 | |||
| bccadc700a | |||
| 1a308f586c | |||
| 2431521daf | |||
| 27cc349703 | |||
| 79f55e3756 | |||
| d3c4cab0c1 | |||
| a2152127a3 | |||
| aeba13e972 | |||
| 716d7e2e94 | |||
| 3ff9710f5f | |||
| f24e5f467a | |||
| 420e1a7a85 | |||
| e73f9cf07a | |||
| 4391eb7e5f | |||
| fa9984f188 | |||
| a8953f2dea | |||
| e106d3cfd2 | |||
| 48014bd0fc | |||
| 772c121f1c | |||
| 9794dc365d | |||
| e08d0912bc | |||
| b258065896 | |||
| 4f92bad5a2 | |||
| 3fbff19137 | |||
| fe5ac8d21a | |||
| a795707afe | |||
| fa7217d7bc | |||
| affbe603cb | |||
| 652e68373f | |||
| 3bf471db23 | |||
| dffd5f0387 | |||
| 9feef2fb70 | |||
| c41444216a | |||
| 2bfc58cb3a | |||
| 540c22d2a3 | |||
| 3769a73861 | |||
| 4f4339e3f0 | |||
| 2f0134d53d | |||
| ab07a478c0 | |||
| d619c5c2df | |||
| 1dd78c8868 | |||
| 7fd763b741 | |||
| 77e43c93ca | |||
| dc1c3d0149 | |||
| 200bf90dae | |||
| ffd2e0d94d | |||
| 9e6e682130 | |||
| 518e4658a7 | |||
| 1b77a49958 | |||
| c7b948f1e8 |
674
LICENCE
Normal file
@@ -0,0 +1,674 @@
|
|||||||
|
GNU GENERAL PUBLIC LICENSE
|
||||||
|
Version 3, 29 June 2007
|
||||||
|
|
||||||
|
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
|
||||||
|
Everyone is permitted to copy and distribute verbatim copies
|
||||||
|
of this license document, but changing it is not allowed.
|
||||||
|
|
||||||
|
Preamble
|
||||||
|
|
||||||
|
The GNU General Public License is a free, copyleft license for
|
||||||
|
software and other kinds of works.
|
||||||
|
|
||||||
|
The licenses for most software and other practical works are designed
|
||||||
|
to take away your freedom to share and change the works. By contrast,
|
||||||
|
the GNU General Public License is intended to guarantee your freedom to
|
||||||
|
share and change all versions of a program--to make sure it remains free
|
||||||
|
software for all its users. We, the Free Software Foundation, use the
|
||||||
|
GNU General Public License for most of our software; it applies also to
|
||||||
|
any other work released this way by its authors. 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
|
||||||
|
them 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 prevent others from denying you
|
||||||
|
these rights or asking you to surrender the rights. Therefore, you have
|
||||||
|
certain responsibilities if you distribute copies of the software, or if
|
||||||
|
you modify it: responsibilities to respect the freedom of others.
|
||||||
|
|
||||||
|
For example, if you distribute copies of such a program, whether
|
||||||
|
gratis or for a fee, you must pass on to the recipients the same
|
||||||
|
freedoms that you received. 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.
|
||||||
|
|
||||||
|
Developers that use the GNU GPL protect your rights with two steps:
|
||||||
|
(1) assert copyright on the software, and (2) offer you this License
|
||||||
|
giving you legal permission to copy, distribute and/or modify it.
|
||||||
|
|
||||||
|
For the developers' and authors' protection, the GPL clearly explains
|
||||||
|
that there is no warranty for this free software. For both users' and
|
||||||
|
authors' sake, the GPL requires that modified versions be marked as
|
||||||
|
changed, so that their problems will not be attributed erroneously to
|
||||||
|
authors of previous versions.
|
||||||
|
|
||||||
|
Some devices are designed to deny users access to install or run
|
||||||
|
modified versions of the software inside them, although the manufacturer
|
||||||
|
can do so. This is fundamentally incompatible with the aim of
|
||||||
|
protecting users' freedom to change the software. The systematic
|
||||||
|
pattern of such abuse occurs in the area of products for individuals to
|
||||||
|
use, which is precisely where it is most unacceptable. Therefore, we
|
||||||
|
have designed this version of the GPL to prohibit the practice for those
|
||||||
|
products. If such problems arise substantially in other domains, we
|
||||||
|
stand ready to extend this provision to those domains in future versions
|
||||||
|
of the GPL, as needed to protect the freedom of users.
|
||||||
|
|
||||||
|
Finally, every program is threatened constantly by software patents.
|
||||||
|
States should not allow patents to restrict development and use of
|
||||||
|
software on general-purpose computers, but in those that do, we wish to
|
||||||
|
avoid the special danger that patents applied to a free program could
|
||||||
|
make it effectively proprietary. To prevent this, the GPL assures that
|
||||||
|
patents cannot be used to render the program non-free.
|
||||||
|
|
||||||
|
The precise terms and conditions for copying, distribution and
|
||||||
|
modification follow.
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
0. Definitions.
|
||||||
|
|
||||||
|
"This License" refers to version 3 of the GNU General Public License.
|
||||||
|
|
||||||
|
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||||
|
works, such as semiconductor masks.
|
||||||
|
|
||||||
|
"The Program" refers to any copyrightable work licensed under this
|
||||||
|
License. Each licensee is addressed as "you". "Licensees" and
|
||||||
|
"recipients" may be individuals or organizations.
|
||||||
|
|
||||||
|
To "modify" a work means to copy from or adapt all or part of the work
|
||||||
|
in a fashion requiring copyright permission, other than the making of an
|
||||||
|
exact copy. The resulting work is called a "modified version" of the
|
||||||
|
earlier work or a work "based on" the earlier work.
|
||||||
|
|
||||||
|
A "covered work" means either the unmodified Program or a work based
|
||||||
|
on the Program.
|
||||||
|
|
||||||
|
To "propagate" a work means to do anything with it that, without
|
||||||
|
permission, would make you directly or secondarily liable for
|
||||||
|
infringement under applicable copyright law, except executing it on a
|
||||||
|
computer or modifying a private copy. Propagation includes copying,
|
||||||
|
distribution (with or without modification), making available to the
|
||||||
|
public, and in some countries other activities as well.
|
||||||
|
|
||||||
|
To "convey" a work means any kind of propagation that enables other
|
||||||
|
parties to make or receive copies. Mere interaction with a user through
|
||||||
|
a computer network, with no transfer of a copy, is not conveying.
|
||||||
|
|
||||||
|
An interactive user interface displays "Appropriate Legal Notices"
|
||||||
|
to the extent that it includes a convenient and prominently visible
|
||||||
|
feature that (1) displays an appropriate copyright notice, and (2)
|
||||||
|
tells the user that there is no warranty for the work (except to the
|
||||||
|
extent that warranties are provided), that licensees may convey the
|
||||||
|
work under this License, and how to view a copy of this License. If
|
||||||
|
the interface presents a list of user commands or options, such as a
|
||||||
|
menu, a prominent item in the list meets this criterion.
|
||||||
|
|
||||||
|
1. Source Code.
|
||||||
|
|
||||||
|
The "source code" for a work means the preferred form of the work
|
||||||
|
for making modifications to it. "Object code" means any non-source
|
||||||
|
form of a work.
|
||||||
|
|
||||||
|
A "Standard Interface" means an interface that either is an official
|
||||||
|
standard defined by a recognized standards body, or, in the case of
|
||||||
|
interfaces specified for a particular programming language, one that
|
||||||
|
is widely used among developers working in that language.
|
||||||
|
|
||||||
|
The "System Libraries" of an executable work include anything, other
|
||||||
|
than the work as a whole, that (a) is included in the normal form of
|
||||||
|
packaging a Major Component, but which is not part of that Major
|
||||||
|
Component, and (b) serves only to enable use of the work with that
|
||||||
|
Major Component, or to implement a Standard Interface for which an
|
||||||
|
implementation is available to the public in source code form. A
|
||||||
|
"Major Component", in this context, means a major essential component
|
||||||
|
(kernel, window system, and so on) of the specific operating system
|
||||||
|
(if any) on which the executable work runs, or a compiler used to
|
||||||
|
produce the work, or an object code interpreter used to run it.
|
||||||
|
|
||||||
|
The "Corresponding Source" for a work in object code form means all
|
||||||
|
the source code needed to generate, install, and (for an executable
|
||||||
|
work) run the object code and to modify the work, including scripts to
|
||||||
|
control those activities. However, it does not include the work's
|
||||||
|
System Libraries, or general-purpose tools or generally available free
|
||||||
|
programs which are used unmodified in performing those activities but
|
||||||
|
which are not part of the work. For example, Corresponding Source
|
||||||
|
includes interface definition files associated with source files for
|
||||||
|
the work, and the source code for shared libraries and dynamically
|
||||||
|
linked subprograms that the work is specifically designed to require,
|
||||||
|
such as by intimate data communication or control flow between those
|
||||||
|
subprograms and other parts of the work.
|
||||||
|
|
||||||
|
The Corresponding Source need not include anything that users
|
||||||
|
can regenerate automatically from other parts of the Corresponding
|
||||||
|
Source.
|
||||||
|
|
||||||
|
The Corresponding Source for a work in source code form is that
|
||||||
|
same work.
|
||||||
|
|
||||||
|
2. Basic Permissions.
|
||||||
|
|
||||||
|
All rights granted under this License are granted for the term of
|
||||||
|
copyright on the Program, and are irrevocable provided the stated
|
||||||
|
conditions are met. This License explicitly affirms your unlimited
|
||||||
|
permission to run the unmodified Program. The output from running a
|
||||||
|
covered work is covered by this License only if the output, given its
|
||||||
|
content, constitutes a covered work. This License acknowledges your
|
||||||
|
rights of fair use or other equivalent, as provided by copyright law.
|
||||||
|
|
||||||
|
You may make, run and propagate covered works that you do not
|
||||||
|
convey, without conditions so long as your license otherwise remains
|
||||||
|
in force. You may convey covered works to others for the sole purpose
|
||||||
|
of having them make modifications exclusively for you, or provide you
|
||||||
|
with facilities for running those works, provided that you comply with
|
||||||
|
the terms of this License in conveying all material for which you do
|
||||||
|
not control copyright. Those thus making or running the covered works
|
||||||
|
for you must do so exclusively on your behalf, under your direction
|
||||||
|
and control, on terms that prohibit them from making any copies of
|
||||||
|
your copyrighted material outside their relationship with you.
|
||||||
|
|
||||||
|
Conveying under any other circumstances is permitted solely under
|
||||||
|
the conditions stated below. Sublicensing is not allowed; section 10
|
||||||
|
makes it unnecessary.
|
||||||
|
|
||||||
|
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||||
|
|
||||||
|
No covered work shall be deemed part of an effective technological
|
||||||
|
measure under any applicable law fulfilling obligations under article
|
||||||
|
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||||
|
similar laws prohibiting or restricting circumvention of such
|
||||||
|
measures.
|
||||||
|
|
||||||
|
When you convey a covered work, you waive any legal power to forbid
|
||||||
|
circumvention of technological measures to the extent such circumvention
|
||||||
|
is effected by exercising rights under this License with respect to
|
||||||
|
the covered work, and you disclaim any intention to limit operation or
|
||||||
|
modification of the work as a means of enforcing, against the work's
|
||||||
|
users, your or third parties' legal rights to forbid circumvention of
|
||||||
|
technological measures.
|
||||||
|
|
||||||
|
4. Conveying Verbatim Copies.
|
||||||
|
|
||||||
|
You may convey 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;
|
||||||
|
keep intact all notices stating that this License and any
|
||||||
|
non-permissive terms added in accord with section 7 apply to the code;
|
||||||
|
keep intact all notices of the absence of any warranty; and give all
|
||||||
|
recipients a copy of this License along with the Program.
|
||||||
|
|
||||||
|
You may charge any price or no price for each copy that you convey,
|
||||||
|
and you may offer support or warranty protection for a fee.
|
||||||
|
|
||||||
|
5. Conveying Modified Source Versions.
|
||||||
|
|
||||||
|
You may convey a work based on the Program, or the modifications to
|
||||||
|
produce it from the Program, in the form of source code under the
|
||||||
|
terms of section 4, provided that you also meet all of these conditions:
|
||||||
|
|
||||||
|
a) The work must carry prominent notices stating that you modified
|
||||||
|
it, and giving a relevant date.
|
||||||
|
|
||||||
|
b) The work must carry prominent notices stating that it is
|
||||||
|
released under this License and any conditions added under section
|
||||||
|
7. This requirement modifies the requirement in section 4 to
|
||||||
|
"keep intact all notices".
|
||||||
|
|
||||||
|
c) You must license the entire work, as a whole, under this
|
||||||
|
License to anyone who comes into possession of a copy. This
|
||||||
|
License will therefore apply, along with any applicable section 7
|
||||||
|
additional terms, to the whole of the work, and all its parts,
|
||||||
|
regardless of how they are packaged. This License gives no
|
||||||
|
permission to license the work in any other way, but it does not
|
||||||
|
invalidate such permission if you have separately received it.
|
||||||
|
|
||||||
|
d) If the work has interactive user interfaces, each must display
|
||||||
|
Appropriate Legal Notices; however, if the Program has interactive
|
||||||
|
interfaces that do not display Appropriate Legal Notices, your
|
||||||
|
work need not make them do so.
|
||||||
|
|
||||||
|
A compilation of a covered work with other separate and independent
|
||||||
|
works, which are not by their nature extensions of the covered work,
|
||||||
|
and which are not combined with it such as to form a larger program,
|
||||||
|
in or on a volume of a storage or distribution medium, is called an
|
||||||
|
"aggregate" if the compilation and its resulting copyright are not
|
||||||
|
used to limit the access or legal rights of the compilation's users
|
||||||
|
beyond what the individual works permit. Inclusion of a covered work
|
||||||
|
in an aggregate does not cause this License to apply to the other
|
||||||
|
parts of the aggregate.
|
||||||
|
|
||||||
|
6. Conveying Non-Source Forms.
|
||||||
|
|
||||||
|
You may convey a covered work in object code form under the terms
|
||||||
|
of sections 4 and 5, provided that you also convey the
|
||||||
|
machine-readable Corresponding Source under the terms of this License,
|
||||||
|
in one of these ways:
|
||||||
|
|
||||||
|
a) Convey the object code in, or embodied in, a physical product
|
||||||
|
(including a physical distribution medium), accompanied by the
|
||||||
|
Corresponding Source fixed on a durable physical medium
|
||||||
|
customarily used for software interchange.
|
||||||
|
|
||||||
|
b) Convey the object code in, or embodied in, a physical product
|
||||||
|
(including a physical distribution medium), accompanied by a
|
||||||
|
written offer, valid for at least three years and valid for as
|
||||||
|
long as you offer spare parts or customer support for that product
|
||||||
|
model, to give anyone who possesses the object code either (1) a
|
||||||
|
copy of the Corresponding Source for all the software in the
|
||||||
|
product that is covered by this License, on a durable physical
|
||||||
|
medium customarily used for software interchange, for a price no
|
||||||
|
more than your reasonable cost of physically performing this
|
||||||
|
conveying of source, or (2) access to copy the
|
||||||
|
Corresponding Source from a network server at no charge.
|
||||||
|
|
||||||
|
c) Convey individual copies of the object code with a copy of the
|
||||||
|
written offer to provide the Corresponding Source. This
|
||||||
|
alternative is allowed only occasionally and noncommercially, and
|
||||||
|
only if you received the object code with such an offer, in accord
|
||||||
|
with subsection 6b.
|
||||||
|
|
||||||
|
d) Convey the object code by offering access from a designated
|
||||||
|
place (gratis or for a charge), and offer equivalent access to the
|
||||||
|
Corresponding Source in the same way through the same place at no
|
||||||
|
further charge. You need not require recipients to copy the
|
||||||
|
Corresponding Source along with the object code. If the place to
|
||||||
|
copy the object code is a network server, the Corresponding Source
|
||||||
|
may be on a different server (operated by you or a third party)
|
||||||
|
that supports equivalent copying facilities, provided you maintain
|
||||||
|
clear directions next to the object code saying where to find the
|
||||||
|
Corresponding Source. Regardless of what server hosts the
|
||||||
|
Corresponding Source, you remain obligated to ensure that it is
|
||||||
|
available for as long as needed to satisfy these requirements.
|
||||||
|
|
||||||
|
e) Convey the object code using peer-to-peer transmission, provided
|
||||||
|
you inform other peers where the object code and Corresponding
|
||||||
|
Source of the work are being offered to the general public at no
|
||||||
|
charge under subsection 6d.
|
||||||
|
|
||||||
|
A separable portion of the object code, whose source code is excluded
|
||||||
|
from the Corresponding Source as a System Library, need not be
|
||||||
|
included in conveying the object code work.
|
||||||
|
|
||||||
|
A "User Product" is either (1) a "consumer product", which means any
|
||||||
|
tangible personal property which is normally used for personal, family,
|
||||||
|
or household purposes, or (2) anything designed or sold for incorporation
|
||||||
|
into a dwelling. In determining whether a product is a consumer product,
|
||||||
|
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||||
|
product received by a particular user, "normally used" refers to a
|
||||||
|
typical or common use of that class of product, regardless of the status
|
||||||
|
of the particular user or of the way in which the particular user
|
||||||
|
actually uses, or expects or is expected to use, the product. A product
|
||||||
|
is a consumer product regardless of whether the product has substantial
|
||||||
|
commercial, industrial or non-consumer uses, unless such uses represent
|
||||||
|
the only significant mode of use of the product.
|
||||||
|
|
||||||
|
"Installation Information" for a User Product means any methods,
|
||||||
|
procedures, authorization keys, or other information required to install
|
||||||
|
and execute modified versions of a covered work in that User Product from
|
||||||
|
a modified version of its Corresponding Source. The information must
|
||||||
|
suffice to ensure that the continued functioning of the modified object
|
||||||
|
code is in no case prevented or interfered with solely because
|
||||||
|
modification has been made.
|
||||||
|
|
||||||
|
If you convey an object code work under this section in, or with, or
|
||||||
|
specifically for use in, a User Product, and the conveying occurs as
|
||||||
|
part of a transaction in which the right of possession and use of the
|
||||||
|
User Product is transferred to the recipient in perpetuity or for a
|
||||||
|
fixed term (regardless of how the transaction is characterized), the
|
||||||
|
Corresponding Source conveyed under this section must be accompanied
|
||||||
|
by the Installation Information. But this requirement does not apply
|
||||||
|
if neither you nor any third party retains the ability to install
|
||||||
|
modified object code on the User Product (for example, the work has
|
||||||
|
been installed in ROM).
|
||||||
|
|
||||||
|
The requirement to provide Installation Information does not include a
|
||||||
|
requirement to continue to provide support service, warranty, or updates
|
||||||
|
for a work that has been modified or installed by the recipient, or for
|
||||||
|
the User Product in which it has been modified or installed. Access to a
|
||||||
|
network may be denied when the modification itself materially and
|
||||||
|
adversely affects the operation of the network or violates the rules and
|
||||||
|
protocols for communication across the network.
|
||||||
|
|
||||||
|
Corresponding Source conveyed, and Installation Information provided,
|
||||||
|
in accord with this section must be in a format that is publicly
|
||||||
|
documented (and with an implementation available to the public in
|
||||||
|
source code form), and must require no special password or key for
|
||||||
|
unpacking, reading or copying.
|
||||||
|
|
||||||
|
7. Additional Terms.
|
||||||
|
|
||||||
|
"Additional permissions" are terms that supplement the terms of this
|
||||||
|
License by making exceptions from one or more of its conditions.
|
||||||
|
Additional permissions that are applicable to the entire Program shall
|
||||||
|
be treated as though they were included in this License, to the extent
|
||||||
|
that they are valid under applicable law. If additional permissions
|
||||||
|
apply only to part of the Program, that part may be used separately
|
||||||
|
under those permissions, but the entire Program remains governed by
|
||||||
|
this License without regard to the additional permissions.
|
||||||
|
|
||||||
|
When you convey a copy of a covered work, you may at your option
|
||||||
|
remove any additional permissions from that copy, or from any part of
|
||||||
|
it. (Additional permissions may be written to require their own
|
||||||
|
removal in certain cases when you modify the work.) You may place
|
||||||
|
additional permissions on material, added by you to a covered work,
|
||||||
|
for which you have or can give appropriate copyright permission.
|
||||||
|
|
||||||
|
Notwithstanding any other provision of this License, for material you
|
||||||
|
add to a covered work, you may (if authorized by the copyright holders of
|
||||||
|
that material) supplement the terms of this License with terms:
|
||||||
|
|
||||||
|
a) Disclaiming warranty or limiting liability differently from the
|
||||||
|
terms of sections 15 and 16 of this License; or
|
||||||
|
|
||||||
|
b) Requiring preservation of specified reasonable legal notices or
|
||||||
|
author attributions in that material or in the Appropriate Legal
|
||||||
|
Notices displayed by works containing it; or
|
||||||
|
|
||||||
|
c) Prohibiting misrepresentation of the origin of that material, or
|
||||||
|
requiring that modified versions of such material be marked in
|
||||||
|
reasonable ways as different from the original version; or
|
||||||
|
|
||||||
|
d) Limiting the use for publicity purposes of names of licensors or
|
||||||
|
authors of the material; or
|
||||||
|
|
||||||
|
e) Declining to grant rights under trademark law for use of some
|
||||||
|
trade names, trademarks, or service marks; or
|
||||||
|
|
||||||
|
f) Requiring indemnification of licensors and authors of that
|
||||||
|
material by anyone who conveys the material (or modified versions of
|
||||||
|
it) with contractual assumptions of liability to the recipient, for
|
||||||
|
any liability that these contractual assumptions directly impose on
|
||||||
|
those licensors and authors.
|
||||||
|
|
||||||
|
All other non-permissive additional terms are considered "further
|
||||||
|
restrictions" within the meaning of section 10. If the Program as you
|
||||||
|
received it, or any part of it, contains a notice stating that it is
|
||||||
|
governed by this License along with a term that is a further
|
||||||
|
restriction, you may remove that term. If a license document contains
|
||||||
|
a further restriction but permits relicensing or conveying under this
|
||||||
|
License, you may add to a covered work material governed by the terms
|
||||||
|
of that license document, provided that the further restriction does
|
||||||
|
not survive such relicensing or conveying.
|
||||||
|
|
||||||
|
If you add terms to a covered work in accord with this section, you
|
||||||
|
must place, in the relevant source files, a statement of the
|
||||||
|
additional terms that apply to those files, or a notice indicating
|
||||||
|
where to find the applicable terms.
|
||||||
|
|
||||||
|
Additional terms, permissive or non-permissive, may be stated in the
|
||||||
|
form of a separately written license, or stated as exceptions;
|
||||||
|
the above requirements apply either way.
|
||||||
|
|
||||||
|
8. Termination.
|
||||||
|
|
||||||
|
You may not propagate or modify a covered work except as expressly
|
||||||
|
provided under this License. Any attempt otherwise to propagate or
|
||||||
|
modify it is void, and will automatically terminate your rights under
|
||||||
|
this License (including any patent licenses granted under the third
|
||||||
|
paragraph of section 11).
|
||||||
|
|
||||||
|
However, if you cease all violation of this License, then your
|
||||||
|
license from a particular copyright holder is reinstated (a)
|
||||||
|
provisionally, unless and until the copyright holder explicitly and
|
||||||
|
finally terminates your license, and (b) permanently, if the copyright
|
||||||
|
holder fails to notify you of the violation by some reasonable means
|
||||||
|
prior to 60 days after the cessation.
|
||||||
|
|
||||||
|
Moreover, your license from a particular copyright holder is
|
||||||
|
reinstated permanently if the copyright holder notifies you of the
|
||||||
|
violation by some reasonable means, this is the first time you have
|
||||||
|
received notice of violation of this License (for any work) from that
|
||||||
|
copyright holder, and you cure the violation prior to 30 days after
|
||||||
|
your receipt of the notice.
|
||||||
|
|
||||||
|
Termination of your rights under this section does not terminate the
|
||||||
|
licenses of parties who have received copies or rights from you under
|
||||||
|
this License. If your rights have been terminated and not permanently
|
||||||
|
reinstated, you do not qualify to receive new licenses for the same
|
||||||
|
material under section 10.
|
||||||
|
|
||||||
|
9. Acceptance Not Required for Having Copies.
|
||||||
|
|
||||||
|
You are not required to accept this License in order to receive or
|
||||||
|
run a copy of the Program. Ancillary propagation of a covered work
|
||||||
|
occurring solely as a consequence of using peer-to-peer transmission
|
||||||
|
to receive a copy likewise does not require acceptance. However,
|
||||||
|
nothing other than this License grants you permission to propagate or
|
||||||
|
modify any covered work. These actions infringe copyright if you do
|
||||||
|
not accept this License. Therefore, by modifying or propagating a
|
||||||
|
covered work, you indicate your acceptance of this License to do so.
|
||||||
|
|
||||||
|
10. Automatic Licensing of Downstream Recipients.
|
||||||
|
|
||||||
|
Each time you convey a covered work, the recipient automatically
|
||||||
|
receives a license from the original licensors, to run, modify and
|
||||||
|
propagate that work, subject to this License. You are not responsible
|
||||||
|
for enforcing compliance by third parties with this License.
|
||||||
|
|
||||||
|
An "entity transaction" is a transaction transferring control of an
|
||||||
|
organization, or substantially all assets of one, or subdividing an
|
||||||
|
organization, or merging organizations. If propagation of a covered
|
||||||
|
work results from an entity transaction, each party to that
|
||||||
|
transaction who receives a copy of the work also receives whatever
|
||||||
|
licenses to the work the party's predecessor in interest had or could
|
||||||
|
give under the previous paragraph, plus a right to possession of the
|
||||||
|
Corresponding Source of the work from the predecessor in interest, if
|
||||||
|
the predecessor has it or can get it with reasonable efforts.
|
||||||
|
|
||||||
|
You may not impose any further restrictions on the exercise of the
|
||||||
|
rights granted or affirmed under this License. For example, you may
|
||||||
|
not impose a license fee, royalty, or other charge for exercise of
|
||||||
|
rights granted under this License, and you may not initiate litigation
|
||||||
|
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||||
|
any patent claim is infringed by making, using, selling, offering for
|
||||||
|
sale, or importing the Program or any portion of it.
|
||||||
|
|
||||||
|
11. Patents.
|
||||||
|
|
||||||
|
A "contributor" is a copyright holder who authorizes use under this
|
||||||
|
License of the Program or a work on which the Program is based. The
|
||||||
|
work thus licensed is called the contributor's "contributor version".
|
||||||
|
|
||||||
|
A contributor's "essential patent claims" are all patent claims
|
||||||
|
owned or controlled by the contributor, whether already acquired or
|
||||||
|
hereafter acquired, that would be infringed by some manner, permitted
|
||||||
|
by this License, of making, using, or selling its contributor version,
|
||||||
|
but do not include claims that would be infringed only as a
|
||||||
|
consequence of further modification of the contributor version. For
|
||||||
|
purposes of this definition, "control" includes the right to grant
|
||||||
|
patent sublicenses in a manner consistent with the requirements of
|
||||||
|
this License.
|
||||||
|
|
||||||
|
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||||
|
patent license under the contributor's essential patent claims, to
|
||||||
|
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||||
|
propagate the contents of its contributor version.
|
||||||
|
|
||||||
|
In the following three paragraphs, a "patent license" is any express
|
||||||
|
agreement or commitment, however denominated, not to enforce a patent
|
||||||
|
(such as an express permission to practice a patent or covenant not to
|
||||||
|
sue for patent infringement). To "grant" such a patent license to a
|
||||||
|
party means to make such an agreement or commitment not to enforce a
|
||||||
|
patent against the party.
|
||||||
|
|
||||||
|
If you convey a covered work, knowingly relying on a patent license,
|
||||||
|
and the Corresponding Source of the work is not available for anyone
|
||||||
|
to copy, free of charge and under the terms of this License, through a
|
||||||
|
publicly available network server or other readily accessible means,
|
||||||
|
then you must either (1) cause the Corresponding Source to be so
|
||||||
|
available, or (2) arrange to deprive yourself of the benefit of the
|
||||||
|
patent license for this particular work, or (3) arrange, in a manner
|
||||||
|
consistent with the requirements of this License, to extend the patent
|
||||||
|
license to downstream recipients. "Knowingly relying" means you have
|
||||||
|
actual knowledge that, but for the patent license, your conveying the
|
||||||
|
covered work in a country, or your recipient's use of the covered work
|
||||||
|
in a country, would infringe one or more identifiable patents in that
|
||||||
|
country that you have reason to believe are valid.
|
||||||
|
|
||||||
|
If, pursuant to or in connection with a single transaction or
|
||||||
|
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||||
|
covered work, and grant a patent license to some of the parties
|
||||||
|
receiving the covered work authorizing them to use, propagate, modify
|
||||||
|
or convey a specific copy of the covered work, then the patent license
|
||||||
|
you grant is automatically extended to all recipients of the covered
|
||||||
|
work and works based on it.
|
||||||
|
|
||||||
|
A patent license is "discriminatory" if it does not include within
|
||||||
|
the scope of its coverage, prohibits the exercise of, or is
|
||||||
|
conditioned on the non-exercise of one or more of the rights that are
|
||||||
|
specifically granted under this License. You may not convey a covered
|
||||||
|
work if you are a party to an arrangement with a third party that is
|
||||||
|
in the business of distributing software, under which you make payment
|
||||||
|
to the third party based on the extent of your activity of conveying
|
||||||
|
the work, and under which the third party grants, to any of the
|
||||||
|
parties who would receive the covered work from you, a discriminatory
|
||||||
|
patent license (a) in connection with copies of the covered work
|
||||||
|
conveyed by you (or copies made from those copies), or (b) primarily
|
||||||
|
for and in connection with specific products or compilations that
|
||||||
|
contain the covered work, unless you entered into that arrangement,
|
||||||
|
or that patent license was granted, prior to 28 March 2007.
|
||||||
|
|
||||||
|
Nothing in this License shall be construed as excluding or limiting
|
||||||
|
any implied license or other defenses to infringement that may
|
||||||
|
otherwise be available to you under applicable patent law.
|
||||||
|
|
||||||
|
12. No Surrender of Others' Freedom.
|
||||||
|
|
||||||
|
If 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 convey a
|
||||||
|
covered work so as to satisfy simultaneously your obligations under this
|
||||||
|
License and any other pertinent obligations, then as a consequence you may
|
||||||
|
not convey it at all. For example, if you agree to terms that obligate you
|
||||||
|
to collect a royalty for further conveying from those to whom you convey
|
||||||
|
the Program, the only way you could satisfy both those terms and this
|
||||||
|
License would be to refrain entirely from conveying the Program.
|
||||||
|
|
||||||
|
13. Use with the GNU Affero General Public License.
|
||||||
|
|
||||||
|
Notwithstanding any other provision of this License, you have
|
||||||
|
permission to link or combine any covered work with a work licensed
|
||||||
|
under version 3 of the GNU Affero General Public License into a single
|
||||||
|
combined work, and to convey the resulting work. The terms of this
|
||||||
|
License will continue to apply to the part which is the covered work,
|
||||||
|
but the special requirements of the GNU Affero General Public License,
|
||||||
|
section 13, concerning interaction through a network will apply to the
|
||||||
|
combination as such.
|
||||||
|
|
||||||
|
14. Revised Versions of this License.
|
||||||
|
|
||||||
|
The Free Software Foundation may publish revised and/or new versions of
|
||||||
|
the GNU 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 that a certain numbered version of the GNU General
|
||||||
|
Public License "or any later version" applies to it, you have the
|
||||||
|
option of following the terms and conditions either of that numbered
|
||||||
|
version or of any later version published by the Free Software
|
||||||
|
Foundation. If the Program does not specify a version number of the
|
||||||
|
GNU General Public License, you may choose any version ever published
|
||||||
|
by the Free Software Foundation.
|
||||||
|
|
||||||
|
If the Program specifies that a proxy can decide which future
|
||||||
|
versions of the GNU General Public License can be used, that proxy's
|
||||||
|
public statement of acceptance of a version permanently authorizes you
|
||||||
|
to choose that version for the Program.
|
||||||
|
|
||||||
|
Later license versions may give you additional or different
|
||||||
|
permissions. However, no additional obligations are imposed on any
|
||||||
|
author or copyright holder as a result of your choosing to follow a
|
||||||
|
later version.
|
||||||
|
|
||||||
|
15. Disclaimer of Warranty.
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
16. Limitation of Liability.
|
||||||
|
|
||||||
|
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||||
|
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||||
|
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.
|
||||||
|
|
||||||
|
17. Interpretation of Sections 15 and 16.
|
||||||
|
|
||||||
|
If the disclaimer of warranty and limitation of liability provided
|
||||||
|
above cannot be given local legal effect according to their terms,
|
||||||
|
reviewing courts shall apply local law that most closely approximates
|
||||||
|
an absolute waiver of all civil liability in connection with the
|
||||||
|
Program, unless a warranty or assumption of liability accompanies a
|
||||||
|
copy of the Program in return for a fee.
|
||||||
|
|
||||||
|
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
|
||||||
|
state 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
Also add information on how to contact you by electronic and paper mail.
|
||||||
|
|
||||||
|
If the program does terminal interaction, make it output a short
|
||||||
|
notice like this when it starts in an interactive mode:
|
||||||
|
|
||||||
|
<program> Copyright (C) <year> <name of author>
|
||||||
|
This program 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, your program's commands
|
||||||
|
might be different; for a GUI interface, you would use an "about box".
|
||||||
|
|
||||||
|
You should also get your employer (if you work as a programmer) or school,
|
||||||
|
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||||
|
For more information on this, and how to apply and follow the GNU GPL, see
|
||||||
|
<http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
The GNU 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. But first, please read
|
||||||
|
<http://www.gnu.org/philosophy/why-not-lgpl.html>.
|
||||||
164
README
Normal file
@@ -0,0 +1,164 @@
|
|||||||
|
pyGTKtalog 1.0
|
||||||
|
==============
|
||||||
|
|
||||||
|
pyGTKtalog is Linux/FreeBSD program for indexing CD/DVD or directories on
|
||||||
|
filesystem. It is similar to gtktalog <http://www.nongnu.org/gtktalog/> or
|
||||||
|
gwhere <http://www.gwhere.org/home.php3>. There is no coincidence in name of
|
||||||
|
application, because it's ment to be replacement (in some way) for gtktalog,
|
||||||
|
which seems to be dead project for years.
|
||||||
|
|
||||||
|
FEATURES
|
||||||
|
========
|
||||||
|
|
||||||
|
- scan for files in selected media
|
||||||
|
- get/generate thumbnails from exif and other images
|
||||||
|
- most important exif tags
|
||||||
|
- add/edit description and notes
|
||||||
|
- fetch comments for images made in gThumb <http://gthumb.sourceforge.net>
|
||||||
|
- add/remove unlimited images to any file or directory
|
||||||
|
- tagging files <http://en.wikipedia.org/wiki/Tag_%28metadata%29>
|
||||||
|
- and more :)
|
||||||
|
|
||||||
|
REQUIREMENTS
|
||||||
|
============
|
||||||
|
|
||||||
|
pyGTKtalog is written in python with following dependencies:
|
||||||
|
|
||||||
|
- python 2.5 or higher
|
||||||
|
- pygtk 2.12 or higher <http://www.pygtk.org>
|
||||||
|
|
||||||
|
Optional modules:
|
||||||
|
|
||||||
|
- PIL <http://www.pythonware.com/products/pil/index.htm> for image manipulation
|
||||||
|
|
||||||
|
Additional pyGTKtalog uses pygtkmvc <http://pygtkmvc.sourceforge.net> by Roberto
|
||||||
|
Cavada and EXIF module by Gene Cash (slightly updatetd to EXIF 2.2 by me) which
|
||||||
|
are included in sources.
|
||||||
|
|
||||||
|
pyGTKtalog extensivly uses external programs in unix spirit, however there is
|
||||||
|
small possibility of using it Windows (probably with limitations) and quite big
|
||||||
|
possiblity to run it on other sofisticated unix-like systems (i.e.
|
||||||
|
BeOS/ZETA/Haiku, QNX or MacOSX).
|
||||||
|
|
||||||
|
INSTALATION
|
||||||
|
===========
|
||||||
|
|
||||||
|
You don't have to install it if you don't want to. You can just change current
|
||||||
|
directory to pyGTKtalog and simply run:
|
||||||
|
|
||||||
|
./pyGTKtalog
|
||||||
|
|
||||||
|
That's it. Alternatively, if you like to put it in more system wide place, all
|
||||||
|
you have to do is:
|
||||||
|
|
||||||
|
- put pyGTKtalog directory into your destination of choice (/usr/local/share,
|
||||||
|
/opt or ~/ is typical bet)
|
||||||
|
- copy pyGTKtalog shell script to /usr/bin, /usr/local/bin or in
|
||||||
|
other place, where PATH variable is pointing or you feel like.
|
||||||
|
- then modify pyGTKtalog line 6 to match right pygtktalog.py directory
|
||||||
|
|
||||||
|
Then, just run pyGTKtalog script.
|
||||||
|
|
||||||
|
TODO
|
||||||
|
====
|
||||||
|
|
||||||
|
PyGTKtalog is still under heavy development, however there is small chance to
|
||||||
|
change structure of catalogs (and if it'll change, there will be transparent
|
||||||
|
function to update DB schema).
|
||||||
|
|
||||||
|
For version 1.0 there are no features to be done, just bug fixes.
|
||||||
|
|
||||||
|
There are still minor aims for versions 1.x to be done:
|
||||||
|
- consolidate popup-menus with edit menu
|
||||||
|
- add popup menu for directly removing tag from tag cloud
|
||||||
|
- implement advanced search
|
||||||
|
|
||||||
|
For version 2.0:
|
||||||
|
- Export/Import
|
||||||
|
- Icon grid in files view
|
||||||
|
- command line support: query, adding media to collection etc
|
||||||
|
- internationalization
|
||||||
|
- export to XLS
|
||||||
|
- user definied group of tags (represented by color in cloud tag)
|
||||||
|
- hiding specified files - configurable, like dot prefixed, cfg and manualy
|
||||||
|
selected
|
||||||
|
- tests
|
||||||
|
- warning about existing image in media directory
|
||||||
|
Removed:
|
||||||
|
- filetypes handling (movies, images, archives, documents etc). Now it have
|
||||||
|
common, unified external "plugin" system - simple text output from command
|
||||||
|
line programs.
|
||||||
|
- anime/movie
|
||||||
|
- title
|
||||||
|
- alt title
|
||||||
|
- type (anime movie, movie, anime oav, anime tv series, tv series, etc)
|
||||||
|
- cover/images
|
||||||
|
- genre
|
||||||
|
- lang
|
||||||
|
- sub lang
|
||||||
|
- release date (from - to)
|
||||||
|
- anidb link/imdb link
|
||||||
|
Maybe in future versions. Now text file descriptions/notes and tags have to
|
||||||
|
be enough for good and fast information search.
|
||||||
|
|
||||||
|
NOTES
|
||||||
|
=====
|
||||||
|
|
||||||
|
Catalog file is plain sqlite database (optionally compressed with bzip2). All
|
||||||
|
images are stored in ~/.pygtktalog/images directory. Names for images are
|
||||||
|
generated sha512 hash from image file itself. There is small possibility for two
|
||||||
|
identical hash for different image files. However, no images are overwritten.
|
||||||
|
Thumbnail filename for each image is simply concatenation of image filename in
|
||||||
|
images directory and '_t' string.
|
||||||
|
|
||||||
|
There is also converter from old database to new for internal use only. In
|
||||||
|
public release there will be no other formats so it will be useless, and
|
||||||
|
deleted. There are some issues with converting. All thumbnails will be lost. All
|
||||||
|
images without big image will be lost. There are serious changes with
|
||||||
|
application design, and I decided, that is better to keep media unpacked on
|
||||||
|
disk, instead of pack it every time with save and unpack with open methods. New
|
||||||
|
design prevent from deleting any file from media directory (placed in
|
||||||
|
~/.pygtktalog/images). Functionality for exporting images and corresponding db
|
||||||
|
file is planned.
|
||||||
|
|
||||||
|
UPDATE
|
||||||
|
------
|
||||||
|
There can be added images for virtually any item in catalog. Therefore there is
|
||||||
|
some hazard with image filenames.
|
||||||
|
After long consideration and experiments I've decided, that images for every
|
||||||
|
item will have file name as follows:
|
||||||
|
|
||||||
|
sha512("filename" + "file size" + "file modification date").hexdigest()
|
||||||
|
|
||||||
|
for thumbnails:
|
||||||
|
sha512("filename" + "file size" + "file modification date").hexdigest() + "_t"
|
||||||
|
|
||||||
|
Why that way? There is plenty ways to achive goal to keep thumbnails/data with
|
||||||
|
applications, however I wanted to keep all things in one place, just to prevent
|
||||||
|
mixing this up with existing, system specific (Gnome, KDE, maybe MacOS, or any
|
||||||
|
other which is capable to run this application) own solution. Another reason
|
||||||
|
lays on catalogs update mechanizm. Imagine, that you have large collection of
|
||||||
|
movie clips and want to frequently add and/or delete somethong from that.
|
||||||
|
Changing file names of virtually all files is rather rare case. However moving
|
||||||
|
them between directories will be much more frequent scenario. And now, if you
|
||||||
|
want to update things in catalog, program will just check if there is such
|
||||||
|
generated image from movie filename, size and dates, and then it will just
|
||||||
|
assign that image to file in catalog. No need to wase time again for generating
|
||||||
|
movie shots all over again.
|
||||||
|
|
||||||
|
Of course there are some limits for such approach. There is relatively small
|
||||||
|
possibility to generate two filenames that are the same in two cases:
|
||||||
|
|
||||||
|
1. There are two different files (movies or images) with the same name, same
|
||||||
|
size and same timestamp in different directories. This could happen in case of
|
||||||
|
images that have fixed size (like BMP) and then due to image/thumbnail creating
|
||||||
|
policy only the first one will be placed in images directory.
|
||||||
|
|
||||||
|
2. Another possibility........ fuck.
|
||||||
|
|
||||||
|
|
||||||
|
BUGS
|
||||||
|
====
|
||||||
|
|
||||||
|
All bugs please report to Roman 'gryf' Dobosz <roman.dobosz@gmail.com>
|
||||||
|
|
||||||
5
cleanup.sh
Executable file
@@ -0,0 +1,5 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# remove ~, pyc, pyo files from current directory
|
||||||
|
find . -name \*~ -exec rm '{}' ';'
|
||||||
|
find . -name \*pyc -exec rm '{}' ';'
|
||||||
|
find . -name \*pyo -exec rm '{}' ';'
|
||||||
97
config.py
@@ -1,97 +0,0 @@
|
|||||||
#!/usr/bin/python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
|
||||||
config parser object
|
|
||||||
"""
|
|
||||||
|
|
||||||
#{{{ podstawowe importy i sprawdzenia
|
|
||||||
import sys
|
|
||||||
import os
|
|
||||||
from ConfigParser import ConfigParser
|
|
||||||
|
|
||||||
class Ini(object):
|
|
||||||
def __init__(self):
|
|
||||||
self.ini = []
|
|
||||||
|
|
||||||
def add_section(self, section):
|
|
||||||
self.ini.append("[%s]" % section)
|
|
||||||
|
|
||||||
def add_key(self, key, value):
|
|
||||||
self.ini.append("%s=%s" % (key, value))
|
|
||||||
|
|
||||||
def add_comment(self, comment):
|
|
||||||
self.ini.append(";%s" % comment)
|
|
||||||
|
|
||||||
def add_verb(self, verb):
|
|
||||||
self.ini.append(verb)
|
|
||||||
|
|
||||||
def show(self):
|
|
||||||
return "\n".join(self.ini)
|
|
||||||
|
|
||||||
|
|
||||||
class Config:
|
|
||||||
ini = Ini()
|
|
||||||
|
|
||||||
confd = {
|
|
||||||
'wx' : 800,
|
|
||||||
'wy' : 600,
|
|
||||||
'h' : 200,
|
|
||||||
'v' : 300,
|
|
||||||
'exportxls' : False,
|
|
||||||
'cd' : '/cdrom',
|
|
||||||
}
|
|
||||||
|
|
||||||
dictconf = {
|
|
||||||
"main window width" : "wx",
|
|
||||||
"main window height": "wy",
|
|
||||||
"horizontal panes": "h",
|
|
||||||
"vertical panes":"v",
|
|
||||||
"export xls":"exportxls",
|
|
||||||
"cd drive":"cd",
|
|
||||||
}
|
|
||||||
|
|
||||||
dbool = ('exportxls')
|
|
||||||
dstring = ('cd')
|
|
||||||
|
|
||||||
try:
|
|
||||||
path = os.environ['HOME']
|
|
||||||
except:
|
|
||||||
path = "/tmp"
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
self.load()
|
|
||||||
|
|
||||||
def save(self):
|
|
||||||
self.confd
|
|
||||||
newIni = Ini()
|
|
||||||
newIni.add_section("pyGTKtalog conf")
|
|
||||||
for opt in self.dictconf:
|
|
||||||
newIni.add_key(opt,self.confd[self.dictconf[opt]])
|
|
||||||
try:
|
|
||||||
f = open("%s/.pygtktalog" % self.path,"w")
|
|
||||||
except:
|
|
||||||
print "Cannot open config file %s for writing." % (self.path, "/.pygtktalog")
|
|
||||||
f.write(newIni.show())
|
|
||||||
f.close()
|
|
||||||
|
|
||||||
def load(self):
|
|
||||||
try:
|
|
||||||
# try to read config file
|
|
||||||
parser = ConfigParser()
|
|
||||||
parser.read("%s/.pygtktalog" % self.path)
|
|
||||||
for sec in parser.sections():
|
|
||||||
if sec == 'pyGTKtalog conf':
|
|
||||||
for opt in parser.options(sec):
|
|
||||||
try:
|
|
||||||
if self.dictconf[opt] in self.dbool:
|
|
||||||
self.confd[self.dictconf[opt]] = parser.getboolean(sec,opt)
|
|
||||||
elif self.dictconf[opt] in self.dstring:
|
|
||||||
self.confd[self.dictconf[opt]] = parser.get(sec,opt)
|
|
||||||
else:
|
|
||||||
self.confd[self.dictconf[opt]] = parser.getint(sec,opt)
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
except:
|
|
||||||
print "No config file"
|
|
||||||
pass
|
|
||||||
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
#!/usr/bin/python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
|
||||||
device (cd, dvd) helper
|
|
||||||
"""
|
|
||||||
|
|
||||||
import string
|
|
||||||
import os
|
|
||||||
|
|
||||||
def volname(dev):
|
|
||||||
"""read volume name from cd/dvd"""
|
|
||||||
try:
|
|
||||||
a = open(dev,"rb")
|
|
||||||
a.seek(32808)
|
|
||||||
b = a.read(32).strip()
|
|
||||||
a.close()
|
|
||||||
except:
|
|
||||||
return None
|
|
||||||
return b
|
|
||||||
|
|
||||||
def volmount(dev):
|
|
||||||
"""mount device, return True/False"""
|
|
||||||
stout,stin,sterr = popen3("mount %s" % dev)
|
|
||||||
error = sterr.readline()
|
|
||||||
stout.close()
|
|
||||||
stin.close()
|
|
||||||
sterr.close()
|
|
||||||
return len(error) == 0
|
|
||||||
23
extract_gettext.sh
Executable file
@@ -0,0 +1,23 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# Create new messages.pot file
|
||||||
|
blacklist="EXIF.py no_thumb.py"
|
||||||
|
rm locale/pygtktalog.pot
|
||||||
|
xgettext -L Python --keyword=_ -o locale/pygtktalog.pot pygtktalog.py
|
||||||
|
|
||||||
|
svn ls | grep '.py$'|grep -v 'pygtktalog.py' |while read file; do
|
||||||
|
xgettext -j -L Python --keyword=_ -o locale/pygtktalog.pot $file
|
||||||
|
done
|
||||||
|
|
||||||
|
for dir in src/ctrls src/models src/views src/lib; do
|
||||||
|
svn ls -R $dir| grep '.py$' |grep -v 'EXIF.py'|grep -v 'no_thumb.py'| while read file; do
|
||||||
|
xgettext -j -L Python --keyword=_ -o locale/pygtktalog.pot $dir/$file
|
||||||
|
done
|
||||||
|
done
|
||||||
|
|
||||||
|
cd locale
|
||||||
|
msginit --input=pygtktalog.pot --locale=pl_PL.UTF-8
|
||||||
|
|
||||||
|
# now its time to make .mo files:
|
||||||
|
mkdir -p pl_PL/LC_MESSAGES
|
||||||
|
#msgfmt --output-file=pl_PL/LC_MESSAGES/pygtktalog.mo pl.po
|
||||||
|
|
||||||
394
glade/main.glade
@@ -1,394 +0,0 @@
|
|||||||
<?xml version="1.0" standalone="no"?> <!--*- mode: xml -*-->
|
|
||||||
<!DOCTYPE glade-interface SYSTEM "http://glade.gnome.org/glade-2.0.dtd">
|
|
||||||
|
|
||||||
<glade-interface>
|
|
||||||
|
|
||||||
<widget class="GtkWindow" id="main">
|
|
||||||
<property name="title" translatable="yes">mainW</property>
|
|
||||||
<property name="type">GTK_WINDOW_TOPLEVEL</property>
|
|
||||||
<property name="window_position">GTK_WIN_POS_NONE</property>
|
|
||||||
<property name="modal">False</property>
|
|
||||||
<property name="resizable">True</property>
|
|
||||||
<property name="destroy_with_parent">False</property>
|
|
||||||
<property name="decorated">True</property>
|
|
||||||
<property name="skip_taskbar_hint">False</property>
|
|
||||||
<property name="skip_pager_hint">False</property>
|
|
||||||
<property name="type_hint">GDK_WINDOW_TYPE_HINT_NORMAL</property>
|
|
||||||
<property name="gravity">GDK_GRAVITY_NORTH_WEST</property>
|
|
||||||
<signal name="destroy" handler="on_main_destroy_event" last_modification_time="Tue, 22 Aug 2006 19:12:49 GMT"/>
|
|
||||||
|
|
||||||
<child>
|
|
||||||
<widget class="GtkVBox" id="vbox1">
|
|
||||||
<property name="visible">True</property>
|
|
||||||
<property name="homogeneous">False</property>
|
|
||||||
<property name="spacing">0</property>
|
|
||||||
|
|
||||||
<child>
|
|
||||||
<widget class="GtkMenuBar" id="mainMenubar">
|
|
||||||
<property name="visible">True</property>
|
|
||||||
|
|
||||||
<child>
|
|
||||||
<widget class="GtkMenuItem" id="menuitem1">
|
|
||||||
<property name="visible">True</property>
|
|
||||||
<property name="label" translatable="yes">_File</property>
|
|
||||||
<property name="use_underline">True</property>
|
|
||||||
|
|
||||||
<child>
|
|
||||||
<widget class="GtkMenu" id="menuitem1_menu">
|
|
||||||
|
|
||||||
<child>
|
|
||||||
<widget class="GtkImageMenuItem" id="new1">
|
|
||||||
<property name="visible">True</property>
|
|
||||||
<property name="label">gtk-new</property>
|
|
||||||
<property name="use_stock">True</property>
|
|
||||||
<signal name="activate" handler="on_new1_activate" last_modification_time="Wed, 09 Aug 2006 17:12:54 GMT"/>
|
|
||||||
</widget>
|
|
||||||
</child>
|
|
||||||
|
|
||||||
<child>
|
|
||||||
<widget class="GtkImageMenuItem" id="open1">
|
|
||||||
<property name="visible">True</property>
|
|
||||||
<property name="label">gtk-open</property>
|
|
||||||
<property name="use_stock">True</property>
|
|
||||||
<signal name="activate" handler="on_open1_activate" last_modification_time="Wed, 09 Aug 2006 17:12:54 GMT"/>
|
|
||||||
</widget>
|
|
||||||
</child>
|
|
||||||
|
|
||||||
<child>
|
|
||||||
<widget class="GtkImageMenuItem" id="save1">
|
|
||||||
<property name="visible">True</property>
|
|
||||||
<property name="label">gtk-save</property>
|
|
||||||
<property name="use_stock">True</property>
|
|
||||||
<signal name="activate" handler="on_save1_activate" last_modification_time="Wed, 09 Aug 2006 17:12:54 GMT"/>
|
|
||||||
</widget>
|
|
||||||
</child>
|
|
||||||
|
|
||||||
<child>
|
|
||||||
<widget class="GtkImageMenuItem" id="save_as1">
|
|
||||||
<property name="visible">True</property>
|
|
||||||
<property name="label">gtk-save-as</property>
|
|
||||||
<property name="use_stock">True</property>
|
|
||||||
<signal name="activate" handler="on_save_as1_activate" last_modification_time="Wed, 09 Aug 2006 17:12:54 GMT"/>
|
|
||||||
</widget>
|
|
||||||
</child>
|
|
||||||
|
|
||||||
<child>
|
|
||||||
<widget class="GtkSeparatorMenuItem" id="separator1">
|
|
||||||
<property name="visible">True</property>
|
|
||||||
</widget>
|
|
||||||
</child>
|
|
||||||
|
|
||||||
<child>
|
|
||||||
<widget class="GtkMenuItem" id="recent_files1">
|
|
||||||
<property name="visible">True</property>
|
|
||||||
<property name="label" translatable="yes">Recent files</property>
|
|
||||||
<property name="use_underline">True</property>
|
|
||||||
<signal name="activate" handler="on_recent_files1_activate" last_modification_time="Thu, 23 Nov 2006 18:29:26 GMT"/>
|
|
||||||
|
|
||||||
<child>
|
|
||||||
<widget class="GtkMenu" id="recent_files1_menu">
|
|
||||||
</widget>
|
|
||||||
</child>
|
|
||||||
</widget>
|
|
||||||
</child>
|
|
||||||
|
|
||||||
<child>
|
|
||||||
<widget class="GtkSeparatorMenuItem" id="separatormenuitem1">
|
|
||||||
<property name="visible">True</property>
|
|
||||||
</widget>
|
|
||||||
</child>
|
|
||||||
|
|
||||||
<child>
|
|
||||||
<widget class="GtkImageMenuItem" id="quit1">
|
|
||||||
<property name="visible">True</property>
|
|
||||||
<property name="label">gtk-quit</property>
|
|
||||||
<property name="use_stock">True</property>
|
|
||||||
<signal name="activate" handler="on_quit1_activate" last_modification_time="Wed, 09 Aug 2006 17:12:54 GMT"/>
|
|
||||||
</widget>
|
|
||||||
</child>
|
|
||||||
</widget>
|
|
||||||
</child>
|
|
||||||
</widget>
|
|
||||||
</child>
|
|
||||||
|
|
||||||
<child>
|
|
||||||
<widget class="GtkMenuItem" id="menuitem2">
|
|
||||||
<property name="visible">True</property>
|
|
||||||
<property name="label" translatable="yes">_Edit</property>
|
|
||||||
<property name="use_underline">True</property>
|
|
||||||
|
|
||||||
<child>
|
|
||||||
<widget class="GtkMenu" id="menuitem2_menu">
|
|
||||||
|
|
||||||
<child>
|
|
||||||
<widget class="GtkImageMenuItem" id="properties1">
|
|
||||||
<property name="visible">True</property>
|
|
||||||
<property name="label">gtk-properties</property>
|
|
||||||
<property name="use_stock">True</property>
|
|
||||||
<signal name="activate" handler="on_properties1_activate" last_modification_time="Wed, 09 Aug 2006 17:14:08 GMT"/>
|
|
||||||
</widget>
|
|
||||||
</child>
|
|
||||||
|
|
||||||
<child>
|
|
||||||
<widget class="GtkImageMenuItem" id="cut1">
|
|
||||||
<property name="visible">True</property>
|
|
||||||
<property name="label">gtk-cut</property>
|
|
||||||
<property name="use_stock">True</property>
|
|
||||||
<signal name="activate" handler="on_cut1_activate" last_modification_time="Wed, 09 Aug 2006 17:12:54 GMT"/>
|
|
||||||
</widget>
|
|
||||||
</child>
|
|
||||||
|
|
||||||
<child>
|
|
||||||
<widget class="GtkImageMenuItem" id="copy1">
|
|
||||||
<property name="visible">True</property>
|
|
||||||
<property name="label">gtk-copy</property>
|
|
||||||
<property name="use_stock">True</property>
|
|
||||||
<signal name="activate" handler="on_copy1_activate" last_modification_time="Wed, 09 Aug 2006 17:12:54 GMT"/>
|
|
||||||
</widget>
|
|
||||||
</child>
|
|
||||||
|
|
||||||
<child>
|
|
||||||
<widget class="GtkImageMenuItem" id="paste1">
|
|
||||||
<property name="visible">True</property>
|
|
||||||
<property name="label">gtk-paste</property>
|
|
||||||
<property name="use_stock">True</property>
|
|
||||||
<signal name="activate" handler="on_paste1_activate" last_modification_time="Wed, 09 Aug 2006 17:12:54 GMT"/>
|
|
||||||
</widget>
|
|
||||||
</child>
|
|
||||||
|
|
||||||
<child>
|
|
||||||
<widget class="GtkImageMenuItem" id="delete1">
|
|
||||||
<property name="visible">True</property>
|
|
||||||
<property name="label">gtk-delete</property>
|
|
||||||
<property name="use_stock">True</property>
|
|
||||||
<signal name="activate" handler="on_delete1_activate" last_modification_time="Wed, 09 Aug 2006 17:12:54 GMT"/>
|
|
||||||
</widget>
|
|
||||||
</child>
|
|
||||||
</widget>
|
|
||||||
</child>
|
|
||||||
</widget>
|
|
||||||
</child>
|
|
||||||
|
|
||||||
<child>
|
|
||||||
<widget class="GtkMenuItem" id="catalog1">
|
|
||||||
<property name="visible">True</property>
|
|
||||||
<property name="label" translatable="yes">_Catalog</property>
|
|
||||||
<property name="use_underline">True</property>
|
|
||||||
|
|
||||||
<child>
|
|
||||||
<widget class="GtkMenu" id="catalog1_menu">
|
|
||||||
|
|
||||||
<child>
|
|
||||||
<widget class="GtkMenuItem" id="add_cd">
|
|
||||||
<property name="visible">True</property>
|
|
||||||
<property name="label" translatable="yes">Add CD/DVD</property>
|
|
||||||
<property name="use_underline">True</property>
|
|
||||||
<signal name="activate" handler="on_add_cd_activate" last_modification_time="Fri, 24 Nov 2006 14:12:30 GMT"/>
|
|
||||||
</widget>
|
|
||||||
</child>
|
|
||||||
|
|
||||||
<child>
|
|
||||||
<widget class="GtkMenuItem" id="add_directory1">
|
|
||||||
<property name="visible">True</property>
|
|
||||||
<property name="label" translatable="yes">Add Directory</property>
|
|
||||||
<property name="use_underline">True</property>
|
|
||||||
<signal name="activate" handler="on_add_directory1_activate" last_modification_time="Fri, 24 Nov 2006 14:12:30 GMT"/>
|
|
||||||
</widget>
|
|
||||||
</child>
|
|
||||||
</widget>
|
|
||||||
</child>
|
|
||||||
</widget>
|
|
||||||
</child>
|
|
||||||
|
|
||||||
<child>
|
|
||||||
<widget class="GtkMenuItem" id="menuitem3">
|
|
||||||
<property name="visible">True</property>
|
|
||||||
<property name="label" translatable="yes">_View</property>
|
|
||||||
<property name="use_underline">True</property>
|
|
||||||
</widget>
|
|
||||||
</child>
|
|
||||||
|
|
||||||
<child>
|
|
||||||
<widget class="GtkMenuItem" id="menuitem4">
|
|
||||||
<property name="visible">True</property>
|
|
||||||
<property name="label" translatable="yes">_Help</property>
|
|
||||||
<property name="use_underline">True</property>
|
|
||||||
|
|
||||||
<child>
|
|
||||||
<widget class="GtkMenu" id="menuitem4_menu">
|
|
||||||
|
|
||||||
<child>
|
|
||||||
<widget class="GtkMenuItem" id="about1">
|
|
||||||
<property name="visible">True</property>
|
|
||||||
<property name="label" translatable="yes">_About</property>
|
|
||||||
<property name="use_underline">True</property>
|
|
||||||
<signal name="activate" handler="on_about1_activate" last_modification_time="Wed, 09 Aug 2006 17:12:54 GMT"/>
|
|
||||||
</widget>
|
|
||||||
</child>
|
|
||||||
</widget>
|
|
||||||
</child>
|
|
||||||
</widget>
|
|
||||||
</child>
|
|
||||||
</widget>
|
|
||||||
<packing>
|
|
||||||
<property name="padding">0</property>
|
|
||||||
<property name="expand">False</property>
|
|
||||||
<property name="fill">False</property>
|
|
||||||
</packing>
|
|
||||||
</child>
|
|
||||||
|
|
||||||
<child>
|
|
||||||
<widget class="GtkHPaned" id="hpaned1">
|
|
||||||
<property name="visible">True</property>
|
|
||||||
<property name="can_focus">True</property>
|
|
||||||
<property name="position">0</property>
|
|
||||||
|
|
||||||
<child>
|
|
||||||
<widget class="GtkScrolledWindow" id="scrolledwindow1">
|
|
||||||
<property name="visible">True</property>
|
|
||||||
<property name="can_focus">True</property>
|
|
||||||
<property name="hscrollbar_policy">GTK_POLICY_ALWAYS</property>
|
|
||||||
<property name="vscrollbar_policy">GTK_POLICY_ALWAYS</property>
|
|
||||||
<property name="shadow_type">GTK_SHADOW_IN</property>
|
|
||||||
<property name="window_placement">GTK_CORNER_TOP_LEFT</property>
|
|
||||||
|
|
||||||
<child>
|
|
||||||
<widget class="GtkTreeView" id="discs">
|
|
||||||
<property name="visible">True</property>
|
|
||||||
<property name="can_focus">True</property>
|
|
||||||
<property name="headers_visible">False</property>
|
|
||||||
<property name="rules_hint">False</property>
|
|
||||||
<property name="reorderable">False</property>
|
|
||||||
<property name="enable_search">True</property>
|
|
||||||
</widget>
|
|
||||||
</child>
|
|
||||||
</widget>
|
|
||||||
<packing>
|
|
||||||
<property name="shrink">True</property>
|
|
||||||
<property name="resize">False</property>
|
|
||||||
</packing>
|
|
||||||
</child>
|
|
||||||
|
|
||||||
<child>
|
|
||||||
<widget class="GtkVPaned" id="vpaned1">
|
|
||||||
<property name="visible">True</property>
|
|
||||||
<property name="can_focus">True</property>
|
|
||||||
<property name="position">0</property>
|
|
||||||
|
|
||||||
<child>
|
|
||||||
<widget class="GtkScrolledWindow" id="scrolledwindow2">
|
|
||||||
<property name="visible">True</property>
|
|
||||||
<property name="can_focus">True</property>
|
|
||||||
<property name="hscrollbar_policy">GTK_POLICY_ALWAYS</property>
|
|
||||||
<property name="vscrollbar_policy">GTK_POLICY_ALWAYS</property>
|
|
||||||
<property name="shadow_type">GTK_SHADOW_IN</property>
|
|
||||||
<property name="window_placement">GTK_CORNER_TOP_LEFT</property>
|
|
||||||
|
|
||||||
<child>
|
|
||||||
<widget class="GtkTreeView" id="files">
|
|
||||||
<property name="visible">True</property>
|
|
||||||
<property name="can_focus">True</property>
|
|
||||||
<property name="headers_visible">True</property>
|
|
||||||
<property name="rules_hint">False</property>
|
|
||||||
<property name="reorderable">False</property>
|
|
||||||
<property name="enable_search">True</property>
|
|
||||||
</widget>
|
|
||||||
</child>
|
|
||||||
</widget>
|
|
||||||
<packing>
|
|
||||||
<property name="shrink">True</property>
|
|
||||||
<property name="resize">False</property>
|
|
||||||
</packing>
|
|
||||||
</child>
|
|
||||||
|
|
||||||
<child>
|
|
||||||
<widget class="GtkScrolledWindow" id="detailplace">
|
|
||||||
<property name="visible">True</property>
|
|
||||||
<property name="can_focus">True</property>
|
|
||||||
<property name="hscrollbar_policy">GTK_POLICY_ALWAYS</property>
|
|
||||||
<property name="vscrollbar_policy">GTK_POLICY_ALWAYS</property>
|
|
||||||
<property name="shadow_type">GTK_SHADOW_IN</property>
|
|
||||||
<property name="window_placement">GTK_CORNER_TOP_LEFT</property>
|
|
||||||
|
|
||||||
<child>
|
|
||||||
<widget class="GtkTextView" id="details">
|
|
||||||
<property name="visible">True</property>
|
|
||||||
<property name="can_focus">True</property>
|
|
||||||
<property name="editable">False</property>
|
|
||||||
<property name="overwrite">False</property>
|
|
||||||
<property name="accepts_tab">False</property>
|
|
||||||
<property name="justification">GTK_JUSTIFY_LEFT</property>
|
|
||||||
<property name="wrap_mode">GTK_WRAP_NONE</property>
|
|
||||||
<property name="cursor_visible">False</property>
|
|
||||||
<property name="pixels_above_lines">0</property>
|
|
||||||
<property name="pixels_below_lines">0</property>
|
|
||||||
<property name="pixels_inside_wrap">0</property>
|
|
||||||
<property name="left_margin">0</property>
|
|
||||||
<property name="right_margin">0</property>
|
|
||||||
<property name="indent">0</property>
|
|
||||||
<property name="text" translatable="yes"></property>
|
|
||||||
</widget>
|
|
||||||
</child>
|
|
||||||
</widget>
|
|
||||||
<packing>
|
|
||||||
<property name="shrink">True</property>
|
|
||||||
<property name="resize">True</property>
|
|
||||||
</packing>
|
|
||||||
</child>
|
|
||||||
</widget>
|
|
||||||
<packing>
|
|
||||||
<property name="shrink">True</property>
|
|
||||||
<property name="resize">True</property>
|
|
||||||
</packing>
|
|
||||||
</child>
|
|
||||||
</widget>
|
|
||||||
<packing>
|
|
||||||
<property name="padding">0</property>
|
|
||||||
<property name="expand">True</property>
|
|
||||||
<property name="fill">True</property>
|
|
||||||
</packing>
|
|
||||||
</child>
|
|
||||||
|
|
||||||
<child>
|
|
||||||
<widget class="GtkHBox" id="hbox1">
|
|
||||||
<property name="visible">True</property>
|
|
||||||
<property name="homogeneous">False</property>
|
|
||||||
<property name="spacing">0</property>
|
|
||||||
|
|
||||||
<child>
|
|
||||||
<widget class="GtkStatusbar" id="mainStatus">
|
|
||||||
<property name="visible">True</property>
|
|
||||||
<property name="has_resize_grip">False</property>
|
|
||||||
</widget>
|
|
||||||
<packing>
|
|
||||||
<property name="padding">0</property>
|
|
||||||
<property name="expand">True</property>
|
|
||||||
<property name="fill">True</property>
|
|
||||||
</packing>
|
|
||||||
</child>
|
|
||||||
|
|
||||||
<child>
|
|
||||||
<widget class="GtkProgressBar" id="progressbar1">
|
|
||||||
<property name="visible">True</property>
|
|
||||||
<property name="orientation">GTK_PROGRESS_LEFT_TO_RIGHT</property>
|
|
||||||
<property name="fraction">0</property>
|
|
||||||
<property name="pulse_step">0.10000000149</property>
|
|
||||||
</widget>
|
|
||||||
<packing>
|
|
||||||
<property name="padding">0</property>
|
|
||||||
<property name="expand">False</property>
|
|
||||||
<property name="fill">False</property>
|
|
||||||
</packing>
|
|
||||||
</child>
|
|
||||||
</widget>
|
|
||||||
<packing>
|
|
||||||
<property name="padding">0</property>
|
|
||||||
<property name="expand">False</property>
|
|
||||||
<property name="fill">False</property>
|
|
||||||
</packing>
|
|
||||||
</child>
|
|
||||||
</widget>
|
|
||||||
</child>
|
|
||||||
</widget>
|
|
||||||
|
|
||||||
</glade-interface>
|
|
||||||
BIN
img/notavail.gif
|
Before Width: | Height: | Size: 1.9 KiB |
303
locale/pl.po
Normal file
@@ -0,0 +1,303 @@
|
|||||||
|
# Polish translations for PACKAGE package
|
||||||
|
# Polskie tłumaczenia dla pakietu PACKAGE.
|
||||||
|
# Copyright (C) 2009 THE PACKAGE'S COPYRIGHT HOLDER
|
||||||
|
# This file is distributed under the same license as the PACKAGE package.
|
||||||
|
# <gryf73@gmail.com>, 2009.
|
||||||
|
#
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
|
"Report-Msgid-Bugs-To: \n"
|
||||||
|
"POT-Creation-Date: 2009-01-11 13:51+0100\n"
|
||||||
|
"PO-Revision-Date: 2009-01-11 13:51+0100\n"
|
||||||
|
"Last-Translator: <gryf73@gmail.com>\n"
|
||||||
|
"Language-Team: Polish\n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 "
|
||||||
|
"|| n%100>=20) ? 1 : 2);\n"
|
||||||
|
|
||||||
|
#: pygtktalog.py:38
|
||||||
|
msgid ""
|
||||||
|
"WARNING: You'll need Python Imaging Library (PIL), if you want to make "
|
||||||
|
"thumbnails!"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/ctrls/c_main.py:284
|
||||||
|
msgid "Delete thumbnails"
|
||||||
|
msgstr "Usunięcie miniaturek"
|
||||||
|
|
||||||
|
#: src/ctrls/c_main.py:284
|
||||||
|
msgid "Delete thumbnails?"
|
||||||
|
msgstr "Usunąć mniniaturki?"
|
||||||
|
|
||||||
|
#: src/ctrls/c_main.py:285
|
||||||
|
msgid "Thumbnails for selected items will be permanently removed from catalog."
|
||||||
|
msgstr "Miniaturki zaznaczonych obiektów zostaną trwale usunięte z katalogu."
|
||||||
|
|
||||||
|
#: src/ctrls/c_main.py:308 src/ctrls/c_main.py:731 src/ctrls/c_main.py:736
|
||||||
|
msgid "Delete images"
|
||||||
|
msgstr "Usnięcie obrazów"
|
||||||
|
|
||||||
|
#: src/ctrls/c_main.py:308
|
||||||
|
msgid "Delete all images?"
|
||||||
|
msgstr "Usunąć wszystkie obrazy?"
|
||||||
|
|
||||||
|
#: src/ctrls/c_main.py:309
|
||||||
|
msgid "All images for selected items will be permanently removed from catalog."
|
||||||
|
msgstr "Wszystkie obrazy zaznaczonych obiektów zostaną trwale usunięte z katalogu."
|
||||||
|
|
||||||
|
#: src/ctrls/c_main.py:342
|
||||||
|
msgid "Image view"
|
||||||
|
msgstr "PrzeglÄ…darka obrazĂłw"
|
||||||
|
|
||||||
|
#: src/ctrls/c_main.py:342
|
||||||
|
msgid "No Image"
|
||||||
|
msgstr "Brak obrazu"
|
||||||
|
|
||||||
|
#: src/ctrls/c_main.py:343
|
||||||
|
msgid "Image file does not exist."
|
||||||
|
msgstr "Plik obrazu nie istnieje."
|
||||||
|
|
||||||
|
#: src/ctrls/c_main.py:491
|
||||||
|
msgid "Quit application"
|
||||||
|
msgstr "Zakończ aplikację"
|
||||||
|
|
||||||
|
#: src/ctrls/c_main.py:492
|
||||||
|
msgid "Do you really want to quit?"
|
||||||
|
msgstr "Czy naprawdę chcesz zakończyć?"
|
||||||
|
|
||||||
|
#: src/ctrls/c_main.py:493 src/ctrls/c_main.py:506
|
||||||
|
msgid "Current database is not saved, any changes will be lost."
|
||||||
|
msgstr "Bieżąca baza nie została zachowana, wszystkie zmiany zostaną utracone."
|
||||||
|
|
||||||
|
#: src/ctrls/c_main.py:504 src/ctrls/c_main.py:638
|
||||||
|
msgid "Unsaved data"
|
||||||
|
msgstr "Niezapisane dane"
|
||||||
|
|
||||||
|
#: src/ctrls/c_main.py:505
|
||||||
|
msgid "Do you want to abandon changes?"
|
||||||
|
msgstr "Czy chcesz porzucić zmiany?"
|
||||||
|
|
||||||
|
#: src/ctrls/c_main.py:541
|
||||||
|
msgid "Error mounting device"
|
||||||
|
msgstr "Błąd w trakcie podłączania urzadzenia"
|
||||||
|
|
||||||
|
#: src/ctrls/c_main.py:542
|
||||||
|
#, python-format
|
||||||
|
msgid "Cannot mount device pointed to %s"
|
||||||
|
msgstr "Nie można podłączyć urządzenia %s"
|
||||||
|
|
||||||
|
#: src/ctrls/c_main.py:544
|
||||||
|
#, python-format
|
||||||
|
msgid ""
|
||||||
|
"Last mount message:\n"
|
||||||
|
"%s"
|
||||||
|
msgstr "Ostatni komunikat polecenia mount:\n%s"
|
||||||
|
|
||||||
|
#: src/ctrls/c_main.py:616
|
||||||
|
msgid "Error writing file"
|
||||||
|
msgstr "Błąd w zapisie pliku"
|
||||||
|
|
||||||
|
#: src/ctrls/c_main.py:617
|
||||||
|
#, python-format
|
||||||
|
msgid "Cannot write file %s."
|
||||||
|
msgstr "Nie można zapisać pliku %s."
|
||||||
|
|
||||||
|
#: src/ctrls/c_main.py:639
|
||||||
|
msgid "There is not saved database"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/ctrls/c_main.py:640
|
||||||
|
msgid "Pressing <b>Ok</b> will abandon catalog."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/ctrls/c_main.py:664
|
||||||
|
msgid "Error opening file"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/ctrls/c_main.py:665
|
||||||
|
#, python-format
|
||||||
|
msgid "Cannot open file %s."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/ctrls/c_main.py:731
|
||||||
|
msgid "No images selected"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/ctrls/c_main.py:732
|
||||||
|
msgid "You have to select at least one image to delete."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/ctrls/c_main.py:736
|
||||||
|
msgid "Delete selected images?"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/ctrls/c_main.py:737
|
||||||
|
msgid "Selected images will be permanently removed from catalog."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/ctrls/c_main.py:767
|
||||||
|
msgid "Choose directory to save images"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/ctrls/c_main.py:792 src/ctrls/c_main.py:798
|
||||||
|
msgid "Save images"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/ctrls/c_main.py:793
|
||||||
|
#, python-format
|
||||||
|
msgid "%d images was succsefully saved."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/ctrls/c_main.py:794
|
||||||
|
#, python-format
|
||||||
|
msgid ""
|
||||||
|
"Images are placed in directory:\n"
|
||||||
|
"%s."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/ctrls/c_main.py:796
|
||||||
|
msgid "Images probably don't have real images - only thumbnails."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/ctrls/c_main.py:798
|
||||||
|
msgid "No images was saved."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/ctrls/c_main.py:807 src/ctrls/c_main.py:811
|
||||||
|
msgid "Set thumbnail"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/ctrls/c_main.py:807
|
||||||
|
msgid "No image selected"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/ctrls/c_main.py:808 src/ctrls/c_main.py:812
|
||||||
|
msgid "You have to select one image to set as thumbnail."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/ctrls/c_main.py:811
|
||||||
|
msgid "To many images selected"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/ctrls/c_main.py:870
|
||||||
|
msgid "Choose export file"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/ctrls/c_main.py:1016 src/ctrls/c_main.py:1025
|
||||||
|
msgid "Remove tags"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/ctrls/c_main.py:1016 src/ctrls/c_main.py:1167
|
||||||
|
msgid "No files selected"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/ctrls/c_main.py:1017
|
||||||
|
msgid "You have to select some files first."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/ctrls/c_main.py:1025
|
||||||
|
msgid "No tags selected"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/ctrls/c_main.py:1026
|
||||||
|
msgid "You have to select any tag to remove from files."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/ctrls/c_main.py:1052
|
||||||
|
msgid "Don't copy images. Generate only thumbnails."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/ctrls/c_main.py:1116
|
||||||
|
msgid "Delete disc"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/ctrls/c_main.py:1116
|
||||||
|
msgid "No disc selected"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/ctrls/c_main.py:1117
|
||||||
|
msgid "You have to select disc first before you can delete it"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/ctrls/c_main.py:1123
|
||||||
|
#, python-format
|
||||||
|
msgid "Delete %s"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/ctrls/c_main.py:1123
|
||||||
|
#, python-format
|
||||||
|
msgid "Delete %s?"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/ctrls/c_main.py:1124
|
||||||
|
msgid "Object will be permanently removed."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/ctrls/c_main.py:1167 src/ctrls/c_main.py:1172
|
||||||
|
msgid "Delete files"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/ctrls/c_main.py:1168
|
||||||
|
msgid "You have to select at least one file to delete."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/ctrls/c_main.py:1172
|
||||||
|
msgid "Delete files?"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/ctrls/c_main.py:1173
|
||||||
|
msgid ""
|
||||||
|
"Selected files and directories will be permanently\n"
|
||||||
|
" removed from catalog."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/ctrls/c_main.py:1215
|
||||||
|
msgid "Delete thumbnail"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/ctrls/c_main.py:1215
|
||||||
|
msgid "Delete thumbnail?"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/ctrls/c_main.py:1216
|
||||||
|
msgid "Current thumbnail will be permanently removed from catalog."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/ctrls/c_main.py:1364
|
||||||
|
msgid "Error ejecting device"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/ctrls/c_main.py:1366
|
||||||
|
#, python-format
|
||||||
|
msgid "Cannot eject device pointed to %s"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/ctrls/c_main.py:1368
|
||||||
|
#, python-format
|
||||||
|
msgid ""
|
||||||
|
"Last eject message:\n"
|
||||||
|
"%s"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/ctrls/c_main.py:1372
|
||||||
|
msgid "Error unmounting device"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/ctrls/c_main.py:1374
|
||||||
|
#, python-format
|
||||||
|
msgid "Cannot unmount device pointed to %s"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/ctrls/c_main.py:1376
|
||||||
|
#, python-format
|
||||||
|
msgid ""
|
||||||
|
"Last umount message:\n"
|
||||||
|
"%s"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/lib/device_helper.py:85
|
||||||
|
msgid "Eject program not specified"
|
||||||
|
msgstr ""
|
||||||
301
locale/pygtktalog.pot
Normal file
@@ -0,0 +1,301 @@
|
|||||||
|
# SOME DESCRIPTIVE TITLE.
|
||||||
|
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||||
|
# This file is distributed under the same license as the PACKAGE package.
|
||||||
|
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||||
|
#
|
||||||
|
#, fuzzy
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
|
"Report-Msgid-Bugs-To: \n"
|
||||||
|
"POT-Creation-Date: 2009-01-11 13:51+0100\n"
|
||||||
|
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||||
|
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||||
|
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Content-Type: text/plain; charset=CHARSET\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
|
||||||
|
#: pygtktalog.py:38
|
||||||
|
msgid ""
|
||||||
|
"WARNING: You'll need Python Imaging Library (PIL), if you want to make "
|
||||||
|
"thumbnails!"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/ctrls/c_main.py:284
|
||||||
|
msgid "Delete thumbnails"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/ctrls/c_main.py:284
|
||||||
|
msgid "Delete thumbnails?"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/ctrls/c_main.py:285
|
||||||
|
msgid "Thumbnails for selected items will be permanently removed from catalog."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/ctrls/c_main.py:308 src/ctrls/c_main.py:731 src/ctrls/c_main.py:736
|
||||||
|
msgid "Delete images"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/ctrls/c_main.py:308
|
||||||
|
msgid "Delete all images?"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/ctrls/c_main.py:309
|
||||||
|
msgid "All images for selected items will be permanently removed from catalog."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/ctrls/c_main.py:342
|
||||||
|
msgid "Image view"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/ctrls/c_main.py:342
|
||||||
|
msgid "No Image"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/ctrls/c_main.py:343
|
||||||
|
msgid "Image file does not exist."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/ctrls/c_main.py:491
|
||||||
|
msgid "Quit application"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/ctrls/c_main.py:492
|
||||||
|
msgid "Do you really want to quit?"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/ctrls/c_main.py:493 src/ctrls/c_main.py:506
|
||||||
|
msgid "Current database is not saved, any changes will be lost."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/ctrls/c_main.py:504 src/ctrls/c_main.py:638
|
||||||
|
msgid "Unsaved data"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/ctrls/c_main.py:505
|
||||||
|
msgid "Do you want to abandon changes?"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/ctrls/c_main.py:541
|
||||||
|
msgid "Error mounting device"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/ctrls/c_main.py:542
|
||||||
|
#, python-format
|
||||||
|
msgid "Cannot mount device pointed to %s"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/ctrls/c_main.py:544
|
||||||
|
#, python-format
|
||||||
|
msgid ""
|
||||||
|
"Last mount message:\n"
|
||||||
|
"%s"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/ctrls/c_main.py:616
|
||||||
|
msgid "Error writing file"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/ctrls/c_main.py:617
|
||||||
|
#, python-format
|
||||||
|
msgid "Cannot write file %s."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/ctrls/c_main.py:639
|
||||||
|
msgid "There is not saved database"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/ctrls/c_main.py:640
|
||||||
|
msgid "Pressing <b>Ok</b> will abandon catalog."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/ctrls/c_main.py:664
|
||||||
|
msgid "Error opening file"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/ctrls/c_main.py:665
|
||||||
|
#, python-format
|
||||||
|
msgid "Cannot open file %s."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/ctrls/c_main.py:731
|
||||||
|
msgid "No images selected"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/ctrls/c_main.py:732
|
||||||
|
msgid "You have to select at least one image to delete."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/ctrls/c_main.py:736
|
||||||
|
msgid "Delete selected images?"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/ctrls/c_main.py:737
|
||||||
|
msgid "Selected images will be permanently removed from catalog."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/ctrls/c_main.py:767
|
||||||
|
msgid "Choose directory to save images"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/ctrls/c_main.py:792 src/ctrls/c_main.py:798
|
||||||
|
msgid "Save images"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/ctrls/c_main.py:793
|
||||||
|
#, python-format
|
||||||
|
msgid "%d images was succsefully saved."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/ctrls/c_main.py:794
|
||||||
|
#, python-format
|
||||||
|
msgid ""
|
||||||
|
"Images are placed in directory:\n"
|
||||||
|
"%s."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/ctrls/c_main.py:796
|
||||||
|
msgid "Images probably don't have real images - only thumbnails."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/ctrls/c_main.py:798
|
||||||
|
msgid "No images was saved."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/ctrls/c_main.py:807 src/ctrls/c_main.py:811
|
||||||
|
msgid "Set thumbnail"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/ctrls/c_main.py:807
|
||||||
|
msgid "No image selected"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/ctrls/c_main.py:808 src/ctrls/c_main.py:812
|
||||||
|
msgid "You have to select one image to set as thumbnail."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/ctrls/c_main.py:811
|
||||||
|
msgid "To many images selected"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/ctrls/c_main.py:870
|
||||||
|
msgid "Choose export file"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/ctrls/c_main.py:1016 src/ctrls/c_main.py:1025
|
||||||
|
msgid "Remove tags"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/ctrls/c_main.py:1016 src/ctrls/c_main.py:1167
|
||||||
|
msgid "No files selected"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/ctrls/c_main.py:1017
|
||||||
|
msgid "You have to select some files first."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/ctrls/c_main.py:1025
|
||||||
|
msgid "No tags selected"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/ctrls/c_main.py:1026
|
||||||
|
msgid "You have to select any tag to remove from files."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/ctrls/c_main.py:1052
|
||||||
|
msgid "Don't copy images. Generate only thumbnails."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/ctrls/c_main.py:1116
|
||||||
|
msgid "Delete disc"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/ctrls/c_main.py:1116
|
||||||
|
msgid "No disc selected"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/ctrls/c_main.py:1117
|
||||||
|
msgid "You have to select disc first before you can delete it"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/ctrls/c_main.py:1123
|
||||||
|
#, python-format
|
||||||
|
msgid "Delete %s"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/ctrls/c_main.py:1123
|
||||||
|
#, python-format
|
||||||
|
msgid "Delete %s?"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/ctrls/c_main.py:1124
|
||||||
|
msgid "Object will be permanently removed."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/ctrls/c_main.py:1167 src/ctrls/c_main.py:1172
|
||||||
|
msgid "Delete files"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/ctrls/c_main.py:1168
|
||||||
|
msgid "You have to select at least one file to delete."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/ctrls/c_main.py:1172
|
||||||
|
msgid "Delete files?"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/ctrls/c_main.py:1173
|
||||||
|
msgid ""
|
||||||
|
"Selected files and directories will be permanently\n"
|
||||||
|
" removed from catalog."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/ctrls/c_main.py:1215
|
||||||
|
msgid "Delete thumbnail"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/ctrls/c_main.py:1215
|
||||||
|
msgid "Delete thumbnail?"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/ctrls/c_main.py:1216
|
||||||
|
msgid "Current thumbnail will be permanently removed from catalog."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/ctrls/c_main.py:1364
|
||||||
|
msgid "Error ejecting device"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/ctrls/c_main.py:1366
|
||||||
|
#, python-format
|
||||||
|
msgid "Cannot eject device pointed to %s"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/ctrls/c_main.py:1368
|
||||||
|
#, python-format
|
||||||
|
msgid ""
|
||||||
|
"Last eject message:\n"
|
||||||
|
"%s"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/ctrls/c_main.py:1372
|
||||||
|
msgid "Error unmounting device"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/ctrls/c_main.py:1374
|
||||||
|
#, python-format
|
||||||
|
msgid "Cannot unmount device pointed to %s"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/ctrls/c_main.py:1376
|
||||||
|
#, python-format
|
||||||
|
msgid ""
|
||||||
|
"Last umount message:\n"
|
||||||
|
"%s"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/lib/device_helper.py:85
|
||||||
|
msgid "Eject program not specified"
|
||||||
|
msgstr ""
|
||||||
172
mainWindow.py
@@ -1,172 +0,0 @@
|
|||||||
#!/usr/bin/python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
|
||||||
GUI, main window class and correspondig methods for pyGTKtalog app.
|
|
||||||
"""
|
|
||||||
import sys
|
|
||||||
import os
|
|
||||||
import mimetypes
|
|
||||||
import popen2
|
|
||||||
|
|
||||||
import pygtk
|
|
||||||
import gtk
|
|
||||||
import gtk.glade
|
|
||||||
from config import Config
|
|
||||||
|
|
||||||
class PyGTKtalog:
|
|
||||||
def __init__(self):
|
|
||||||
|
|
||||||
self.conf = Config()
|
|
||||||
|
|
||||||
self.gladefile = "glade/main.glade"
|
|
||||||
self.pygtkcat = gtk.glade.XML(self.gladefile,"main")
|
|
||||||
|
|
||||||
self.window = self.pygtkcat.get_widget("main")
|
|
||||||
self.window.set_title("pyGTKtalog")
|
|
||||||
icon = gtk.gdk.pixbuf_new_from_file("pixmaps/mainicon.png")
|
|
||||||
self.window.set_icon_list(icon)
|
|
||||||
|
|
||||||
self.progress = self.pygtkcat.get_widget("progressbar1")
|
|
||||||
|
|
||||||
self.status = self.pygtkcat.get_widget("mainStatus")
|
|
||||||
self.sbSearchCId = self.status.get_context_id('detailed res')
|
|
||||||
self.sbid = self.status.push(self.sbSearchCId, "Idle")
|
|
||||||
|
|
||||||
self.detailplaceholder = self.pygtkcat.get_widget("detailplace")
|
|
||||||
self.detailplaceholder.set_sensitive(False)
|
|
||||||
self.details = self.pygtkcat.get_widget("details")
|
|
||||||
self.details.hide()
|
|
||||||
|
|
||||||
self.widgets = ("discs","files","details",'save1','save_as1','cut1','copy1','paste1','delete1','add_cd','add_directory1')
|
|
||||||
for w in self.widgets:
|
|
||||||
a = self.pygtkcat.get_widget(w)
|
|
||||||
a.set_sensitive(False)
|
|
||||||
|
|
||||||
a = self.pygtkcat.get_widget('hpaned1')
|
|
||||||
a.set_position(self.conf.confd['h'])
|
|
||||||
a = self.pygtkcat.get_widget('vpaned1')
|
|
||||||
a.set_position(self.conf.confd['v'])
|
|
||||||
self.window.resize(self.conf.confd['wx'],self.conf.confd['wy'])
|
|
||||||
|
|
||||||
# sygnały:
|
|
||||||
dic = {"on_main_destroy_event" :self.doQuit,
|
|
||||||
"on_quit1_activate" :self.doQuit,
|
|
||||||
"on_new1_activate" :self.newDB,
|
|
||||||
"on_add_cd_activate" :self.addCD,
|
|
||||||
}
|
|
||||||
|
|
||||||
# connect signals
|
|
||||||
self.pygtkcat.signal_autoconnect(dic)
|
|
||||||
self.window.connect("delete_event", self.deleteEvent)
|
|
||||||
|
|
||||||
def storeSettings(self):
|
|
||||||
"""Store window size and pane position in config file (using config object)"""
|
|
||||||
hpan = self.pygtkcat.get_widget('hpaned1')
|
|
||||||
vpan = self.pygtkcat.get_widget('vpaned1')
|
|
||||||
|
|
||||||
self.conf.confd['wx'], self.conf.confd['wy'] = self.window.get_size()
|
|
||||||
self.conf.confd['h'],self.conf.confd['v'] = hpan.get_position(), vpan.get_position()
|
|
||||||
|
|
||||||
self.conf.save()
|
|
||||||
|
|
||||||
return
|
|
||||||
|
|
||||||
def doQuit(self, widget):
|
|
||||||
"""quit and save window parameters to config file"""
|
|
||||||
try:
|
|
||||||
if widget.title:
|
|
||||||
pass
|
|
||||||
except:
|
|
||||||
self.storeSettings()
|
|
||||||
gtk.main_quit()
|
|
||||||
return False
|
|
||||||
|
|
||||||
def newDB(self,widget):
|
|
||||||
"""create database in temporary place"""
|
|
||||||
self.window.set_title("untitled - pyGTKtalog")
|
|
||||||
for w in self.widgets:
|
|
||||||
try:
|
|
||||||
a = self.pygtkcat.get_widget(w)
|
|
||||||
a.set_sensitive(True)
|
|
||||||
# PyGTK FAQ entry 23.20
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
while gtk.events_pending():
|
|
||||||
gtk.main_iteration()
|
|
||||||
|
|
||||||
#self.details.set_sensitive(True)
|
|
||||||
#self.details.destroy()
|
|
||||||
|
|
||||||
#self.details = gtk.Button("Press mi or daj");
|
|
||||||
#self.details.set_name("details")
|
|
||||||
|
|
||||||
#self.detailplaceholder.add_with_viewport(self.details)
|
|
||||||
#self.details.show()
|
|
||||||
return
|
|
||||||
|
|
||||||
def deleteEvent(self, widget, event, data=None):
|
|
||||||
"""checkout actual database changed. If so, do the necessary ask."""
|
|
||||||
self.storeSettings()
|
|
||||||
return False
|
|
||||||
|
|
||||||
def run(self):
|
|
||||||
self.window.show();
|
|
||||||
gtk.main()
|
|
||||||
|
|
||||||
def addCD(self,widget):
|
|
||||||
self.scan(self.conf.confd['cd'])
|
|
||||||
|
|
||||||
def scan(self,path):
|
|
||||||
mime = mimetypes.MimeTypes()
|
|
||||||
extensions = ('mkv','avi','ogg','mpg','wmv','mp4')
|
|
||||||
|
|
||||||
count = 0
|
|
||||||
for root,kat,plik in os.walk(path):
|
|
||||||
for p in plik:
|
|
||||||
count+=1
|
|
||||||
|
|
||||||
frac = 1.0/count
|
|
||||||
count = 1
|
|
||||||
#self.progress.set_pulse_step(0)
|
|
||||||
for root,kat,plik in os.walk(path):
|
|
||||||
for p in plik:
|
|
||||||
if p[-3:].lower() in extensions or \
|
|
||||||
mime.guess_type(p)!= (None,None) and \
|
|
||||||
mime.guess_type(p)[0].split("/")[0] == 'video':
|
|
||||||
# video only
|
|
||||||
# TODO: parametrize this loop!
|
|
||||||
info = popen2.popen4('midentify "' + os.path.join(root,p)+'"')[0].readlines()
|
|
||||||
video_format = ''
|
|
||||||
audio_codec = ''
|
|
||||||
video_codec = ''
|
|
||||||
video_x = ''
|
|
||||||
video_y = ''
|
|
||||||
for line in info:
|
|
||||||
l = line.split('=')
|
|
||||||
val = l[1].split('\n')[0]
|
|
||||||
if l[0] == 'ID_VIDEO_FORMAT':
|
|
||||||
video_format = val
|
|
||||||
elif l[0] == 'ID_AUDIO_CODEC':
|
|
||||||
audio_codec = val
|
|
||||||
elif l[0] == 'ID_VIDEO_CODEC':
|
|
||||||
video_codec = val
|
|
||||||
elif l[0] == 'ID_VIDEO_WIDTH':
|
|
||||||
video_x = val
|
|
||||||
elif l[0] == 'ID_VIDEO_HEIGHT':
|
|
||||||
video_y = val
|
|
||||||
if self.sbid != 0:
|
|
||||||
"""jeśli jest jakiś mesedż, usuń go"""
|
|
||||||
self.status.remove(self.sbSearchCId, self.sbid)
|
|
||||||
self.sbid = self.status.push(self.sbSearchCId, "Scannig: %s" % (os.path.join(root,p)))
|
|
||||||
|
|
||||||
self.progress.set_fraction(frac * count)
|
|
||||||
count+=1
|
|
||||||
|
|
||||||
# PyGTK FAQ entry 23.20
|
|
||||||
while gtk.events_pending(): gtk.main_iteration()
|
|
||||||
if self.sbid != 0:
|
|
||||||
self.status.remove(self.sbSearchCId, self.sbid)
|
|
||||||
self.sbid = self.status.push(self.sbSearchCId, "Idle")
|
|
||||||
|
|
||||||
self.progress.set_fraction(0)
|
|
||||||
|
|
||||||
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 3.7 KiB |
BIN
pixmaps/mainicon.xcf
Normal file
BIN
pixmaps/mainicon2.xcf
Normal file
69
prefs_prefs.py
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
import pygtk
|
||||||
|
pygtk.require('2.0')
|
||||||
|
import gtk
|
||||||
|
|
||||||
|
class PreferencesMgr(gtk.Dialog):
|
||||||
|
def __init__(self):
|
||||||
|
gtk.Dialog.__init__(self, 'Preferences', None,
|
||||||
|
gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
|
||||||
|
(gtk.STOCK_OK, gtk.RESPONSE_OK,
|
||||||
|
gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL))
|
||||||
|
self.current_frame = None
|
||||||
|
self.create_gui()
|
||||||
|
|
||||||
|
def create_gui(self):
|
||||||
|
|
||||||
|
model = gtk.ListStore(str, gtk.gdk.Pixbuf)
|
||||||
|
|
||||||
|
pixbuf = gtk.gdk.pixbuf_new_from_file('/usr/share/icons/Buuf/128x128/status/stock_weather-night-clear.png')
|
||||||
|
model.append(['General', pixbuf])
|
||||||
|
|
||||||
|
pixbuf = gtk.gdk.pixbuf_new_from_file('/usr/share/icons/Buuf/128x128/stock/generic/stock_alarm.png')
|
||||||
|
model.append(['Security', pixbuf])
|
||||||
|
|
||||||
|
self.icon_view = gtk.IconView(model)
|
||||||
|
self.icon_view.set_text_column(0)
|
||||||
|
self.icon_view.set_pixbuf_column(1)
|
||||||
|
self.icon_view.set_orientation(gtk.ORIENTATION_VERTICAL)
|
||||||
|
self.icon_view.set_selection_mode(gtk.SELECTION_SINGLE)
|
||||||
|
self.icon_view.connect('selection-changed', self.on_select, model)
|
||||||
|
self.icon_view.set_columns(1)
|
||||||
|
self.icon_view.set_item_width(-1)
|
||||||
|
self.icon_view.set_size_request(72, -1)
|
||||||
|
|
||||||
|
self.content_box = gtk.HBox(False)
|
||||||
|
self.content_box.pack_start(self.icon_view, fill=True, expand=False)
|
||||||
|
self.icon_view.select_path((0,)) # select a category, will create frame
|
||||||
|
self.show_all()
|
||||||
|
self.vbox.pack_start(self.content_box)
|
||||||
|
self.resize(640, 480)
|
||||||
|
self.show_all()
|
||||||
|
|
||||||
|
def on_select(self, icon_view, model=None):
|
||||||
|
selected = icon_view.get_selected_items()
|
||||||
|
if len(selected) == 0: return
|
||||||
|
i = selected[0][0]
|
||||||
|
category = model[i][0]
|
||||||
|
if self.current_frame is not None:
|
||||||
|
self.content_box.remove(self.current_frame)
|
||||||
|
self.current_frame.destroy()
|
||||||
|
self.current_frame = None
|
||||||
|
if category == 'General':
|
||||||
|
self.current_frame = self.create_general_frame()
|
||||||
|
elif category == 'Security':
|
||||||
|
self.current_frame = self.create_security_frame()
|
||||||
|
self.content_box.pack_end(self.current_frame, fill=True, expand=True)
|
||||||
|
self.show_all()
|
||||||
|
|
||||||
|
def create_general_frame(self):
|
||||||
|
frame = gtk.Frame('General')
|
||||||
|
return frame
|
||||||
|
|
||||||
|
def create_security_frame(self):
|
||||||
|
frame = gtk.Frame('Security')
|
||||||
|
return frame
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
p = PreferencesMgr()
|
||||||
|
p.run()
|
||||||
|
p.destroy()
|
||||||
32
prepare_dist_package.sh
Executable file
@@ -0,0 +1,32 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# remove ~, pyc, pyo files from current directory
|
||||||
|
|
||||||
|
mkdir t 2>/dev/null
|
||||||
|
if [ $? != 0 ]; then
|
||||||
|
echo "cannot create directory 't': File exist."
|
||||||
|
echo "Rename it, move or rename, bcoz it's on the way."
|
||||||
|
exit
|
||||||
|
fi
|
||||||
|
cd t
|
||||||
|
alias ls=ls
|
||||||
|
|
||||||
|
PREV=`ls -1 ..|grep bz2|tail -n 1|cut -f '2' -d '_'|cut -f 1 -d '.'`
|
||||||
|
REV=`svn export svn://10.0.0.10/repos/Python/pyGTKtalog pyGTKtalog |tail -n 1|cut -f 3 -d " "|cut -f 1 -d '.'`
|
||||||
|
|
||||||
|
cd pyGTKtalog
|
||||||
|
find . -name \*~ -exec rm '{}' ';'
|
||||||
|
find . -name \*pyc -exec rm '{}' ';'
|
||||||
|
find . -name \*pyo -exec rm '{}' ';'
|
||||||
|
find . -type d -name .svn -exec rm -fr '{}' ';'
|
||||||
|
rm -fr db img
|
||||||
|
rm -fr prepare_dist_package.sh
|
||||||
|
|
||||||
|
svn log -r ${PREV}:HEAD -v svn://10.0.0.10/repos/Python/pyGTKtalog > CHANGELOG
|
||||||
|
|
||||||
|
cd ..
|
||||||
|
|
||||||
|
tar jcf ../pygtktalog_${REV}.tar.bz2 pyGTKtalog
|
||||||
|
|
||||||
|
cd ..
|
||||||
|
rm -fr t
|
||||||
|
|
||||||
37
pyGTKtalog
@@ -1,32 +1,11 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
## gajim
|
# Simple shell wraper to launch pyGTKtalog
|
||||||
##
|
|
||||||
## Contributors for this file:
|
|
||||||
## - Yann Le Boulanger <asterix@lagaule.org>
|
|
||||||
## - Nikos Kouremenos <kourem@gmail.com>
|
|
||||||
##
|
|
||||||
## Copyright (C) 2003-2004 Yann Le Boulanger <asterix@lagaule.org>
|
|
||||||
## Vincent Hanquez <tab@snarc.org>
|
|
||||||
## Copyright (C) 2005 Yann Le Boulanger <asterix@lagaule.org>
|
|
||||||
## Vincent Hanquez <tab@snarc.org>
|
|
||||||
## Nikos Kouremenos <nkour@jabber.org>
|
|
||||||
## Dimitur Kirov <dkirov@gmail.com>
|
|
||||||
## Travis Shirk <travis@pobox.com>
|
|
||||||
## Norman Rasmussen <norman@rasmussen.co.za>
|
|
||||||
##
|
|
||||||
## 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; version 2 only.
|
|
||||||
##
|
|
||||||
## 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.
|
|
||||||
|
|
||||||
if [ `id -u` -eq 0 ]; then
|
# change path to pygtktalog.py file full path, then you can move this shell
|
||||||
echo "You must not launch Gajim as root, it is INSECURE"
|
# script to desired place (like /usr/bin, /usr/local/bin, ~/bin and so on)
|
||||||
fi
|
path_to_gtktalog_directory="."
|
||||||
|
|
||||||
cd /mnt/data/Python/moviedb
|
# python interpreter
|
||||||
#export PYTHONPATH="$PYTHONPATH:/usr/lib/gajim"
|
python_intrpreter="/usr/bin/python"
|
||||||
exec python -OO pyGTKtalog.py $@
|
|
||||||
|
exec $python_intrpreter -OO ${path_to_gtktalog_directory}/pygtktalog.py "$@"
|
||||||
|
|||||||
@@ -1,64 +0,0 @@
|
|||||||
#!/usr/bin/python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
|
||||||
pytGTKtalog.
|
|
||||||
|
|
||||||
A wannabe replacement for excellent gtktalog application.
|
|
||||||
|
|
||||||
pyGTKtalog is an application for catalogue/index files on removable media such
|
|
||||||
a CD or DVD discs, directories on hard disks or network shares.
|
|
||||||
|
|
||||||
"""
|
|
||||||
#{{{ try to import all necessary modules
|
|
||||||
import sys
|
|
||||||
import os
|
|
||||||
|
|
||||||
try:
|
|
||||||
from config import Config
|
|
||||||
except:
|
|
||||||
print "Some fundamental files are missing. try runnig pyGTKtalog in his root directory"
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
conf = Config()
|
|
||||||
|
|
||||||
try:
|
|
||||||
import pygtk
|
|
||||||
#tell pyGTK, if possible, that we want GTKv2
|
|
||||||
pygtk.require("2.0")
|
|
||||||
except:
|
|
||||||
#Some distributions come with GTK2, but not pyGTK
|
|
||||||
pass
|
|
||||||
try:
|
|
||||||
import gtk
|
|
||||||
import gtk.glade
|
|
||||||
except:
|
|
||||||
print "You need to install pyGTK or GTKv2 ",
|
|
||||||
print "or set your PYTHONPATH correctly."
|
|
||||||
print "try: export PYTHONPATH=",
|
|
||||||
print "/usr/local/lib/python2.2/site-packages/"
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
try:
|
|
||||||
from pysqlite2 import dbapi2 as sqlite
|
|
||||||
except:
|
|
||||||
print "pyGTKtalog uses SQLite DB.\nYou'll need to get it and the python bindings as well.\nhttp://www.sqlite.org\nhttp://initd.org/tracker/pysqlite"
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
if conf.confd['exportxls']:
|
|
||||||
try:
|
|
||||||
import pyExcelerator
|
|
||||||
except:
|
|
||||||
print "You'll need pyExcelerator, if you want to export DB to XLS format.\nhttp://sourceforge.net/projects/pyexcelerator"
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
# project modules
|
|
||||||
from mainWindow import PyGTKtalog
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
app=PyGTKtalog()
|
|
||||||
try:
|
|
||||||
app.run()
|
|
||||||
except KeyboardInterrupt:
|
|
||||||
app.storeSettings()
|
|
||||||
gtk.main_quit
|
|
||||||
|
|
||||||
75
pygtktalog.py
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
"""
|
||||||
|
Project: pyGTKtalog
|
||||||
|
Description: Application main launch file.
|
||||||
|
Type: core
|
||||||
|
Author: Roman 'gryf' Dobosz, gryf73@gmail.com
|
||||||
|
Created: 2007-05-01
|
||||||
|
"""
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
import locale
|
||||||
|
import gettext
|
||||||
|
|
||||||
|
import gtk
|
||||||
|
import pygtk
|
||||||
|
pygtk.require("2.0")
|
||||||
|
|
||||||
|
import gtkmvc
|
||||||
|
gtkmvc.require("1.2.2")
|
||||||
|
|
||||||
|
from src.lib.globs import TOPDIR
|
||||||
|
from src.lib.globs import APPL_SHORT_NAME
|
||||||
|
sys.path = [os.path.join(TOPDIR, "src")] + sys.path
|
||||||
|
from models.m_config import ConfigModel
|
||||||
|
from models.m_main import MainModel
|
||||||
|
from ctrls.c_main import MainController
|
||||||
|
from views.v_main import MainView
|
||||||
|
|
||||||
|
def check_requirements():
|
||||||
|
"""Checks versions and other requirements"""
|
||||||
|
|
||||||
|
conf = ConfigModel()
|
||||||
|
conf.load()
|
||||||
|
|
||||||
|
if conf.confd['thumbs'] and conf.confd['retrive']:
|
||||||
|
try:
|
||||||
|
import Image
|
||||||
|
except ImportError:
|
||||||
|
print _("WARNING: You'll need Python Imaging Library (PIL), if "
|
||||||
|
"you want to make thumbnails!")
|
||||||
|
raise
|
||||||
|
return
|
||||||
|
|
||||||
|
def run():
|
||||||
|
"""Create model, controller and view and launch it."""
|
||||||
|
# Directory from where pygtkatalog was invoced. We need it for calculate
|
||||||
|
# path for argument (catalog file)
|
||||||
|
execution_dir = os.path.abspath(os.path.curdir)
|
||||||
|
|
||||||
|
# Directory, where this files lies. We need it to setup private source
|
||||||
|
# paths
|
||||||
|
libraries_dir = os.path.dirname(__file__)
|
||||||
|
if libraries_dir:
|
||||||
|
os.chdir(libraries_dir)
|
||||||
|
|
||||||
|
# Setup i18n
|
||||||
|
locale.setlocale(locale.LC_ALL, '')
|
||||||
|
gettext.install(APPL_SHORT_NAME, 'locale', unicode=True)
|
||||||
|
|
||||||
|
check_requirements()
|
||||||
|
|
||||||
|
model = MainModel()
|
||||||
|
if len(sys.argv) > 1:
|
||||||
|
model.open(os.path.join(execution_dir, sys.argv[1]))
|
||||||
|
controler = MainController(model)
|
||||||
|
view = MainView(controler)
|
||||||
|
|
||||||
|
try:
|
||||||
|
gtk.main()
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
model.config.save()
|
||||||
|
model.cleanup()
|
||||||
|
gtk.main_quit
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
run()
|
||||||
803
resources/glade/config.glade
Normal file
@@ -0,0 +1,803 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!DOCTYPE glade-interface SYSTEM "glade-2.0.dtd">
|
||||||
|
<!--*- mode: xml -*-->
|
||||||
|
<glade-interface>
|
||||||
|
<widget class="GtkDialog" id="config">
|
||||||
|
<property name="width_request">550</property>
|
||||||
|
<property name="height_request">400</property>
|
||||||
|
<property name="title" translatable="yes">Preferences - pyGTKtalog</property>
|
||||||
|
<property name="modal">True</property>
|
||||||
|
<property name="type_hint">GDK_WINDOW_TYPE_HINT_NORMAL</property>
|
||||||
|
<child internal-child="vbox">
|
||||||
|
<widget class="GtkVBox" id="dialog-vbox1">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<child>
|
||||||
|
<widget class="GtkHPaned" id="hpaned1">
|
||||||
|
<property name="width_request">168</property>
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="position">140</property>
|
||||||
|
<child>
|
||||||
|
<widget class="GtkScrolledWindow" id="scrolledwindow1">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="hscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
|
||||||
|
<property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
|
||||||
|
<property name="shadow_type">GTK_SHADOW_IN</property>
|
||||||
|
<child>
|
||||||
|
<widget class="GtkTreeView" id="category_tree">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="headers_visible">False</property>
|
||||||
|
<property name="rules_hint">True</property>
|
||||||
|
<signal name="cursor_changed" handler="on_category_tree_cursor_changed"/>
|
||||||
|
</widget>
|
||||||
|
</child>
|
||||||
|
</widget>
|
||||||
|
<packing>
|
||||||
|
<property name="resize">False</property>
|
||||||
|
<property name="shrink">True</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<widget class="GtkVBox" id="vbox1">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<child>
|
||||||
|
<widget class="GtkLabel" id="desc">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="xpad">3</property>
|
||||||
|
<property name="ypad">3</property>
|
||||||
|
<property name="use_markup">True</property>
|
||||||
|
</widget>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="fill">False</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<widget class="GtkScrolledWindow" id="scrolledwindow2">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="hscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
|
||||||
|
<property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
|
||||||
|
<child>
|
||||||
|
<widget class="GtkViewport" id="pref_group">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<child>
|
||||||
|
<widget class="GtkVBox" id="main_group_holder">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<child>
|
||||||
|
<widget class="GtkVBox" id="disk_group">
|
||||||
|
<property name="border_width">1</property>
|
||||||
|
<child>
|
||||||
|
<widget class="GtkFrame" id="frame2">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="border_width">5</property>
|
||||||
|
<property name="label_xalign">0</property>
|
||||||
|
<child>
|
||||||
|
<widget class="GtkAlignment" id="alignment2">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="has_focus">True</property>
|
||||||
|
<property name="border_width">5</property>
|
||||||
|
<property name="left_padding">12</property>
|
||||||
|
<child>
|
||||||
|
<widget class="GtkTable" id="table1">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="border_width">5</property>
|
||||||
|
<property name="n_rows">2</property>
|
||||||
|
<property name="n_columns">3</property>
|
||||||
|
<property name="column_spacing">3</property>
|
||||||
|
<property name="row_spacing">3</property>
|
||||||
|
<child>
|
||||||
|
<widget class="GtkLabel" id="label4">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="xalign">0</property>
|
||||||
|
<property name="label" translatable="yes">Mount point:</property>
|
||||||
|
<property name="mnemonic_widget">mnt_entry</property>
|
||||||
|
</widget>
|
||||||
|
<packing>
|
||||||
|
<property name="x_options"></property>
|
||||||
|
<property name="y_options"></property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<widget class="GtkEntry" id="mnt_entry">
|
||||||
|
<property name="width_request">100</property>
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
</widget>
|
||||||
|
<packing>
|
||||||
|
<property name="left_attach">1</property>
|
||||||
|
<property name="right_attach">2</property>
|
||||||
|
<property name="y_options"></property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<widget class="GtkButton" id="button_mnt">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="label" translatable="yes">Browse...</property>
|
||||||
|
<property name="use_underline">True</property>
|
||||||
|
<property name="response_id">0</property>
|
||||||
|
<signal name="clicked" handler="on_button_mnt_clicked"/>
|
||||||
|
<signal name="activate" handler="on_button_mnt_activate"/>
|
||||||
|
</widget>
|
||||||
|
<packing>
|
||||||
|
<property name="left_attach">2</property>
|
||||||
|
<property name="right_attach">3</property>
|
||||||
|
<property name="x_options"></property>
|
||||||
|
<property name="y_options"></property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<widget class="GtkEntry" id="ejt_entry">
|
||||||
|
<property name="width_request">100</property>
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
</widget>
|
||||||
|
<packing>
|
||||||
|
<property name="left_attach">1</property>
|
||||||
|
<property name="right_attach">2</property>
|
||||||
|
<property name="top_attach">1</property>
|
||||||
|
<property name="bottom_attach">2</property>
|
||||||
|
<property name="y_options"></property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<widget class="GtkButton" id="button_ejt">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="label" translatable="yes">Browse...</property>
|
||||||
|
<property name="use_underline">True</property>
|
||||||
|
<property name="response_id">0</property>
|
||||||
|
<signal name="clicked" handler="on_button_ejt_clicked"/>
|
||||||
|
<signal name="activate" handler="on_button_ejt_activate"/>
|
||||||
|
</widget>
|
||||||
|
<packing>
|
||||||
|
<property name="left_attach">2</property>
|
||||||
|
<property name="right_attach">3</property>
|
||||||
|
<property name="top_attach">1</property>
|
||||||
|
<property name="bottom_attach">2</property>
|
||||||
|
<property name="x_options"></property>
|
||||||
|
<property name="y_options"></property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<widget class="GtkLabel" id="label5">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="xalign">0</property>
|
||||||
|
<property name="label" translatable="yes">Eject program:</property>
|
||||||
|
<property name="mnemonic_widget">ejt_entry</property>
|
||||||
|
</widget>
|
||||||
|
<packing>
|
||||||
|
<property name="top_attach">1</property>
|
||||||
|
<property name="bottom_attach">2</property>
|
||||||
|
<property name="x_options"></property>
|
||||||
|
<property name="y_options"></property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
</widget>
|
||||||
|
</child>
|
||||||
|
</widget>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<widget class="GtkLabel" id="label2">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="label" translatable="yes"><b>CD/DVD drive options</b></property>
|
||||||
|
<property name="use_markup">True</property>
|
||||||
|
</widget>
|
||||||
|
<packing>
|
||||||
|
<property name="type">label_item</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
</widget>
|
||||||
|
</child>
|
||||||
|
</widget>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<widget class="GtkVBox" id="general_group">
|
||||||
|
<property name="border_width">1</property>
|
||||||
|
<child>
|
||||||
|
<widget class="GtkFrame" id="frame3">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="border_width">5</property>
|
||||||
|
<property name="label_xalign">0</property>
|
||||||
|
<child>
|
||||||
|
<widget class="GtkAlignment" id="alignment3">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="has_focus">True</property>
|
||||||
|
<property name="border_width">5</property>
|
||||||
|
<property name="left_padding">12</property>
|
||||||
|
<child>
|
||||||
|
<widget class="GtkVBox" id="vbox4">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<child>
|
||||||
|
<widget class="GtkCheckButton" id="ch_win">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="label" translatable="yes">Save main window size</property>
|
||||||
|
<property name="use_underline">True</property>
|
||||||
|
<property name="response_id">0</property>
|
||||||
|
<property name="draw_indicator">True</property>
|
||||||
|
</widget>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="fill">False</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<widget class="GtkCheckButton" id="ch_pan">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="label" translatable="yes">Save paned window sizes</property>
|
||||||
|
<property name="use_underline">True</property>
|
||||||
|
<property name="response_id">0</property>
|
||||||
|
<property name="draw_indicator">True</property>
|
||||||
|
</widget>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="fill">False</property>
|
||||||
|
<property name="position">1</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<widget class="GtkCheckButton" id="ch_eject">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="label" translatable="yes">Eject CD/DVD after scan</property>
|
||||||
|
<property name="use_underline">True</property>
|
||||||
|
<property name="response_id">0</property>
|
||||||
|
<property name="draw_indicator">True</property>
|
||||||
|
</widget>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="fill">False</property>
|
||||||
|
<property name="position">2</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<widget class="GtkCheckButton" id="ch_compress">
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="label" translatable="yes">Compress collection</property>
|
||||||
|
<property name="use_underline">True</property>
|
||||||
|
<property name="response_id">0</property>
|
||||||
|
<property name="draw_indicator">True</property>
|
||||||
|
</widget>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="fill">False</property>
|
||||||
|
<property name="position">3</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<widget class="GtkCheckButton" id="ch_imageviewer">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||||
|
<property name="label" translatable="yes">Use external image viewer</property>
|
||||||
|
<property name="response_id">0</property>
|
||||||
|
<property name="draw_indicator">True</property>
|
||||||
|
<signal name="toggled" handler="on_ch_imageviewer_toggled"/>
|
||||||
|
</widget>
|
||||||
|
<packing>
|
||||||
|
<property name="position">4</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<widget class="GtkTable" id="table4">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="border_width">5</property>
|
||||||
|
<property name="n_rows">1</property>
|
||||||
|
<property name="n_columns">3</property>
|
||||||
|
<property name="column_spacing">3</property>
|
||||||
|
<property name="row_spacing">3</property>
|
||||||
|
<child>
|
||||||
|
<widget class="GtkLabel" id="label_imv">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="sensitive">False</property>
|
||||||
|
<property name="xalign">0</property>
|
||||||
|
<property name="label" translatable="yes">Image viewer:</property>
|
||||||
|
<property name="mnemonic_widget">mnt_entry</property>
|
||||||
|
</widget>
|
||||||
|
<packing>
|
||||||
|
<property name="x_options"></property>
|
||||||
|
<property name="y_options"></property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<widget class="GtkEntry" id="entry_imv">
|
||||||
|
<property name="width_request">100</property>
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="sensitive">False</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
</widget>
|
||||||
|
<packing>
|
||||||
|
<property name="left_attach">1</property>
|
||||||
|
<property name="right_attach">2</property>
|
||||||
|
<property name="y_options"></property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<widget class="GtkButton" id="button_imv">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="sensitive">False</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="label" translatable="yes">Browse...</property>
|
||||||
|
<property name="use_underline">True</property>
|
||||||
|
<property name="response_id">0</property>
|
||||||
|
<signal name="clicked" handler="on_button_imv_clicked"/>
|
||||||
|
</widget>
|
||||||
|
<packing>
|
||||||
|
<property name="left_attach">2</property>
|
||||||
|
<property name="right_attach">3</property>
|
||||||
|
<property name="x_options"></property>
|
||||||
|
<property name="y_options"></property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
</widget>
|
||||||
|
<packing>
|
||||||
|
<property name="position">5</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
</widget>
|
||||||
|
</child>
|
||||||
|
</widget>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<widget class="GtkLabel" id="label8">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="label" translatable="yes"><b>General options</b></property>
|
||||||
|
<property name="use_markup">True</property>
|
||||||
|
</widget>
|
||||||
|
<packing>
|
||||||
|
<property name="type">label_item</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
</widget>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<widget class="GtkFrame" id="frame5">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="border_width">5</property>
|
||||||
|
<property name="label_xalign">0</property>
|
||||||
|
<child>
|
||||||
|
<widget class="GtkAlignment" id="alignment5">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="has_focus">True</property>
|
||||||
|
<property name="border_width">5</property>
|
||||||
|
<property name="left_padding">12</property>
|
||||||
|
<child>
|
||||||
|
<widget class="GtkVBox" id="vbox7">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<child>
|
||||||
|
<widget class="GtkCheckButton" id="ch_xls">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="label" translatable="yes">Export to XLS</property>
|
||||||
|
<property name="use_underline">True</property>
|
||||||
|
<property name="response_id">0</property>
|
||||||
|
<property name="draw_indicator">True</property>
|
||||||
|
</widget>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="fill">False</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
</widget>
|
||||||
|
</child>
|
||||||
|
</widget>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<widget class="GtkLabel" id="label9">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="label" translatable="yes"><b>Misc</b></property>
|
||||||
|
<property name="use_markup">True</property>
|
||||||
|
</widget>
|
||||||
|
<packing>
|
||||||
|
<property name="type">label_item</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
</widget>
|
||||||
|
<packing>
|
||||||
|
<property name="position">1</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<widget class="GtkFrame" id="frame6">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="border_width">5</property>
|
||||||
|
<property name="label_xalign">0</property>
|
||||||
|
<child>
|
||||||
|
<widget class="GtkAlignment" id="alignment6">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="has_focus">True</property>
|
||||||
|
<property name="border_width">5</property>
|
||||||
|
<property name="left_padding">12</property>
|
||||||
|
<child>
|
||||||
|
<widget class="GtkVBox" id="vbox8">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<child>
|
||||||
|
<widget class="GtkCheckButton" id="ch_quit">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="label" translatable="yes">Confirm quit if there are unsaved data</property>
|
||||||
|
<property name="use_underline">True</property>
|
||||||
|
<property name="response_id">0</property>
|
||||||
|
<property name="draw_indicator">True</property>
|
||||||
|
</widget>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="fill">False</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<widget class="GtkCheckButton" id="ch_warnnew">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="label" translatable="yes">Confirm "new" if there are unsaved data</property>
|
||||||
|
<property name="use_underline">True</property>
|
||||||
|
<property name="response_id">0</property>
|
||||||
|
<property name="draw_indicator">True</property>
|
||||||
|
</widget>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="fill">False</property>
|
||||||
|
<property name="position">1</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<widget class="GtkCheckButton" id="ch_wrnmount">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="label" translatable="yes">Warn about mount/umount errors</property>
|
||||||
|
<property name="use_underline">True</property>
|
||||||
|
<property name="response_id">0</property>
|
||||||
|
<property name="draw_indicator">True</property>
|
||||||
|
</widget>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="fill">False</property>
|
||||||
|
<property name="position">2</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<widget class="GtkCheckButton" id="ch_wrndel">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="label" translatable="yes">Warn on delete</property>
|
||||||
|
<property name="use_underline">True</property>
|
||||||
|
<property name="response_id">0</property>
|
||||||
|
<property name="draw_indicator">True</property>
|
||||||
|
</widget>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="fill">False</property>
|
||||||
|
<property name="position">3</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
</widget>
|
||||||
|
</child>
|
||||||
|
</widget>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<widget class="GtkLabel" id="label10">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="label" translatable="yes"><b>Confirmations</b></property>
|
||||||
|
<property name="use_markup">True</property>
|
||||||
|
</widget>
|
||||||
|
<packing>
|
||||||
|
<property name="type">label_item</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
</widget>
|
||||||
|
<packing>
|
||||||
|
<property name="position">2</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
</widget>
|
||||||
|
<packing>
|
||||||
|
<property name="position">1</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<widget class="GtkVBox" id="scan_group">
|
||||||
|
<property name="border_width">1</property>
|
||||||
|
<child>
|
||||||
|
<widget class="GtkFrame" id="frame4">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="border_width">5</property>
|
||||||
|
<property name="label_xalign">0</property>
|
||||||
|
<child>
|
||||||
|
<widget class="GtkAlignment" id="alignment4">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="has_focus">True</property>
|
||||||
|
<property name="border_width">5</property>
|
||||||
|
<property name="left_padding">12</property>
|
||||||
|
<child>
|
||||||
|
<widget class="GtkVBox" id="vbox6">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<child>
|
||||||
|
<widget class="GtkCheckButton" id="ch_thumb">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="label" translatable="yes">Create thumbnails for images</property>
|
||||||
|
<property name="use_underline">True</property>
|
||||||
|
<property name="response_id">0</property>
|
||||||
|
<property name="draw_indicator">True</property>
|
||||||
|
</widget>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="fill">False</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<widget class="GtkCheckButton" id="ch_exif">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="label" translatable="yes">Scan EXIF data</property>
|
||||||
|
<property name="use_underline">True</property>
|
||||||
|
<property name="response_id">0</property>
|
||||||
|
<property name="draw_indicator">True</property>
|
||||||
|
</widget>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="fill">False</property>
|
||||||
|
<property name="position">1</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<widget class="GtkCheckButton" id="ch_gthumb">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="label" translatable="yes">Include gThumb image description</property>
|
||||||
|
<property name="use_underline">True</property>
|
||||||
|
<property name="response_id">0</property>
|
||||||
|
<property name="draw_indicator">True</property>
|
||||||
|
</widget>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="fill">False</property>
|
||||||
|
<property name="position">2</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
</widget>
|
||||||
|
</child>
|
||||||
|
</widget>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<widget class="GtkCheckButton" id="ch_retrive">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||||
|
<property name="label" translatable="yes">Retrive extra information</property>
|
||||||
|
<property name="response_id">0</property>
|
||||||
|
<property name="draw_indicator">True</property>
|
||||||
|
<signal name="toggled" handler="on_ch_retrive_toggled"/>
|
||||||
|
</widget>
|
||||||
|
<packing>
|
||||||
|
<property name="type">label_item</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
</widget>
|
||||||
|
</child>
|
||||||
|
</widget>
|
||||||
|
<packing>
|
||||||
|
<property name="position">2</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<widget class="GtkVBox" id="ft_group">
|
||||||
|
<property name="border_width">1</property>
|
||||||
|
<child>
|
||||||
|
<widget class="GtkFrame" id="frame7">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="border_width">5</property>
|
||||||
|
<property name="label_xalign">0</property>
|
||||||
|
<child>
|
||||||
|
<widget class="GtkAlignment" id="alignment7">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="has_focus">True</property>
|
||||||
|
<property name="border_width">5</property>
|
||||||
|
<property name="left_padding">12</property>
|
||||||
|
<child>
|
||||||
|
<widget class="GtkVBox" id="ext_ed">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="border_width">5</property>
|
||||||
|
<child>
|
||||||
|
<widget class="GtkTable" id="table3">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||||
|
<property name="n_rows">2</property>
|
||||||
|
<property name="n_columns">2</property>
|
||||||
|
<property name="row_spacing">3</property>
|
||||||
|
<child>
|
||||||
|
<widget class="GtkEntry" id="ext_entry">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||||
|
</widget>
|
||||||
|
<packing>
|
||||||
|
<property name="left_attach">1</property>
|
||||||
|
<property name="right_attach">2</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<widget class="GtkLabel" id="label12">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||||
|
<property name="label" translatable="yes">Extension:</property>
|
||||||
|
<property name="justify">GTK_JUSTIFY_RIGHT</property>
|
||||||
|
<property name="mnemonic_widget">ext_entry</property>
|
||||||
|
</widget>
|
||||||
|
<packing>
|
||||||
|
<property name="x_options">GTK_FILL</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<widget class="GtkLabel" id="label6">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||||
|
<property name="label" translatable="yes">Command:</property>
|
||||||
|
</widget>
|
||||||
|
<packing>
|
||||||
|
<property name="top_attach">1</property>
|
||||||
|
<property name="bottom_attach">2</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<widget class="GtkEntry" id="com_entry">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||||
|
</widget>
|
||||||
|
<packing>
|
||||||
|
<property name="left_attach">1</property>
|
||||||
|
<property name="right_attach">2</property>
|
||||||
|
<property name="top_attach">1</property>
|
||||||
|
<property name="bottom_attach">2</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
</widget>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<widget class="GtkHButtonBox" id="hbuttonbox1">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||||
|
<property name="border_width">3</property>
|
||||||
|
<property name="layout_style">GTK_BUTTONBOX_END</property>
|
||||||
|
<child>
|
||||||
|
<widget class="GtkButton" id="ext_add">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="receives_default">True</property>
|
||||||
|
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||||
|
<property name="label" translatable="yes">Add/Change</property>
|
||||||
|
<property name="response_id">0</property>
|
||||||
|
<signal name="clicked" handler="on_ext_add_clicked"/>
|
||||||
|
</widget>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<widget class="GtkButton" id="ext_del">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="receives_default">True</property>
|
||||||
|
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||||
|
<property name="label" translatable="yes">Delete</property>
|
||||||
|
<property name="response_id">0</property>
|
||||||
|
<signal name="clicked" handler="on_ext_del_clicked"/>
|
||||||
|
</widget>
|
||||||
|
<packing>
|
||||||
|
<property name="position">1</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
</widget>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="position">1</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<widget class="GtkScrolledWindow" id="scrolledwindow3">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||||
|
<property name="hscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
|
||||||
|
<property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
|
||||||
|
<property name="shadow_type">GTK_SHADOW_IN</property>
|
||||||
|
<child>
|
||||||
|
<widget class="GtkTreeView" id="extension_tree">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||||
|
<property name="rules_hint">True</property>
|
||||||
|
<signal name="cursor_changed" handler="on_extension_tree_cursor_changed"/>
|
||||||
|
</widget>
|
||||||
|
</child>
|
||||||
|
</widget>
|
||||||
|
<packing>
|
||||||
|
<property name="position">2</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
</widget>
|
||||||
|
</child>
|
||||||
|
</widget>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<widget class="GtkLabel" id="label3">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="label" translatable="yes"><b>Files extensions</b></property>
|
||||||
|
<property name="use_markup">True</property>
|
||||||
|
</widget>
|
||||||
|
<packing>
|
||||||
|
<property name="type">label_item</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
</widget>
|
||||||
|
</child>
|
||||||
|
</widget>
|
||||||
|
<packing>
|
||||||
|
<property name="position">3</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
</widget>
|
||||||
|
</child>
|
||||||
|
</widget>
|
||||||
|
</child>
|
||||||
|
</widget>
|
||||||
|
<packing>
|
||||||
|
<property name="position">1</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
</widget>
|
||||||
|
<packing>
|
||||||
|
<property name="resize">True</property>
|
||||||
|
<property name="shrink">True</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
</widget>
|
||||||
|
<packing>
|
||||||
|
<property name="position">2</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child internal-child="action_area">
|
||||||
|
<widget class="GtkHButtonBox" id="dialog-action_area1">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="layout_style">GTK_BUTTONBOX_END</property>
|
||||||
|
<child>
|
||||||
|
<widget class="GtkButton" id="cancelbutton1">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="can_default">True</property>
|
||||||
|
<property name="label">gtk-cancel</property>
|
||||||
|
<property name="use_stock">True</property>
|
||||||
|
<property name="response_id">-6</property>
|
||||||
|
<signal name="clicked" handler="on_cancelbutton_clicked"/>
|
||||||
|
</widget>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<widget class="GtkButton" id="okbutton1">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="can_default">True</property>
|
||||||
|
<property name="label">gtk-save</property>
|
||||||
|
<property name="use_stock">True</property>
|
||||||
|
<property name="response_id">-5</property>
|
||||||
|
<signal name="clicked" handler="on_okbutton_clicked"/>
|
||||||
|
</widget>
|
||||||
|
<packing>
|
||||||
|
<property name="position">1</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
</widget>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="pack_type">GTK_PACK_END</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
</widget>
|
||||||
|
</child>
|
||||||
|
</widget>
|
||||||
|
</glade-interface>
|
||||||
838
resources/glade/dialogs.glade
Normal file
@@ -0,0 +1,838 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!DOCTYPE glade-interface SYSTEM "glade-2.0.dtd">
|
||||||
|
<!--*- mode: xml -*-->
|
||||||
|
<glade-interface>
|
||||||
|
<widget class="GtkDialog" id="inputDialog">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="title" translatable="yes">Disk label - pyGTKtalog</property>
|
||||||
|
<property name="type_hint">GDK_WINDOW_TYPE_HINT_DIALOG</property>
|
||||||
|
<child internal-child="vbox">
|
||||||
|
<widget class="GtkVBox" id="dialog-vbox1">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<child>
|
||||||
|
<widget class="GtkHBox" id="hbox1">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<child>
|
||||||
|
<widget class="GtkLabel" id="label1">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="xpad">3</property>
|
||||||
|
<property name="label" translatable="yes">Disk label</property>
|
||||||
|
</widget>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="fill">False</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<widget class="GtkEntry" id="volname">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="can_default">True</property>
|
||||||
|
<property name="activates_default">True</property>
|
||||||
|
<signal name="activate" handler="on_volname_activate"/>
|
||||||
|
</widget>
|
||||||
|
<packing>
|
||||||
|
<property name="position">1</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
</widget>
|
||||||
|
<packing>
|
||||||
|
<property name="position">2</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child internal-child="action_area">
|
||||||
|
<widget class="GtkHButtonBox" id="dialog-action_area1">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="layout_style">GTK_BUTTONBOX_END</property>
|
||||||
|
<child>
|
||||||
|
<widget class="GtkButton" id="cancelbutton1">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="can_default">True</property>
|
||||||
|
<property name="label">gtk-cancel</property>
|
||||||
|
<property name="use_stock">True</property>
|
||||||
|
<property name="response_id">-6</property>
|
||||||
|
</widget>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<widget class="GtkButton" id="okbutton1">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="has_focus">True</property>
|
||||||
|
<property name="can_default">True</property>
|
||||||
|
<property name="has_default">True</property>
|
||||||
|
<property name="label">gtk-ok</property>
|
||||||
|
<property name="use_stock">True</property>
|
||||||
|
<property name="response_id">-5</property>
|
||||||
|
</widget>
|
||||||
|
<packing>
|
||||||
|
<property name="position">1</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
</widget>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="pack_type">GTK_PACK_END</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
</widget>
|
||||||
|
</child>
|
||||||
|
</widget>
|
||||||
|
<widget class="GtkDialog" id="addDirDialog">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="title" translatable="yes">dialog1</property>
|
||||||
|
<property name="type_hint">GDK_WINDOW_TYPE_HINT_DIALOG</property>
|
||||||
|
<child internal-child="vbox">
|
||||||
|
<widget class="GtkVBox" id="dialog-vbox2">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<child>
|
||||||
|
<widget class="GtkVBox" id="vbox1">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="border_width">3</property>
|
||||||
|
<child>
|
||||||
|
<widget class="GtkFrame" id="frame2">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="border_width">3</property>
|
||||||
|
<property name="label_xalign">0</property>
|
||||||
|
<child>
|
||||||
|
<widget class="GtkAlignment" id="alignment2">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="border_width">3</property>
|
||||||
|
<property name="left_padding">12</property>
|
||||||
|
<child>
|
||||||
|
<widget class="GtkTable" id="table1">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="n_rows">2</property>
|
||||||
|
<property name="n_columns">3</property>
|
||||||
|
<property name="column_spacing">3</property>
|
||||||
|
<property name="row_spacing">3</property>
|
||||||
|
<child>
|
||||||
|
<placeholder/>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<widget class="GtkButton" id="browse">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="label" translatable="yes">Browse...</property>
|
||||||
|
<property name="use_underline">True</property>
|
||||||
|
<property name="response_id">0</property>
|
||||||
|
<signal name="clicked" handler="on_browse_clicked"/>
|
||||||
|
</widget>
|
||||||
|
<packing>
|
||||||
|
<property name="left_attach">2</property>
|
||||||
|
<property name="right_attach">3</property>
|
||||||
|
<property name="top_attach">1</property>
|
||||||
|
<property name="bottom_attach">2</property>
|
||||||
|
<property name="x_options">GTK_FILL</property>
|
||||||
|
<property name="y_options"></property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<widget class="GtkLabel" id="label7">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="xalign">0</property>
|
||||||
|
<property name="label" translatable="yes">Select directory:</property>
|
||||||
|
</widget>
|
||||||
|
<packing>
|
||||||
|
<property name="top_attach">1</property>
|
||||||
|
<property name="bottom_attach">2</property>
|
||||||
|
<property name="x_options">GTK_FILL</property>
|
||||||
|
<property name="y_options"></property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<widget class="GtkLabel" id="label6">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="xalign">0</property>
|
||||||
|
<property name="label" translatable="yes">Disk Label:</property>
|
||||||
|
</widget>
|
||||||
|
<packing>
|
||||||
|
<property name="x_options">GTK_FILL</property>
|
||||||
|
<property name="y_options"></property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<widget class="GtkEntry" id="directory">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
</widget>
|
||||||
|
<packing>
|
||||||
|
<property name="left_attach">1</property>
|
||||||
|
<property name="right_attach">2</property>
|
||||||
|
<property name="top_attach">1</property>
|
||||||
|
<property name="bottom_attach">2</property>
|
||||||
|
<property name="y_options"></property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<widget class="GtkEntry" id="dirvolname">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="text" translatable="yes">New</property>
|
||||||
|
</widget>
|
||||||
|
<packing>
|
||||||
|
<property name="left_attach">1</property>
|
||||||
|
<property name="right_attach">2</property>
|
||||||
|
<property name="y_options"></property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
</widget>
|
||||||
|
</child>
|
||||||
|
</widget>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<widget class="GtkLabel" id="label4">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="label" translatable="yes"><b>Select directory and enter label</b></property>
|
||||||
|
<property name="use_markup">True</property>
|
||||||
|
</widget>
|
||||||
|
<packing>
|
||||||
|
<property name="type">label_item</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
</widget>
|
||||||
|
</child>
|
||||||
|
</widget>
|
||||||
|
<packing>
|
||||||
|
<property name="position">2</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child internal-child="action_area">
|
||||||
|
<widget class="GtkHButtonBox" id="dialog-action_area2">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="layout_style">GTK_BUTTONBOX_END</property>
|
||||||
|
<child>
|
||||||
|
<widget class="GtkButton" id="cancelbutton2">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="can_default">True</property>
|
||||||
|
<property name="label">gtk-cancel</property>
|
||||||
|
<property name="use_stock">True</property>
|
||||||
|
<property name="response_id">-6</property>
|
||||||
|
</widget>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<widget class="GtkButton" id="okbutton2">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="can_default">True</property>
|
||||||
|
<property name="label">gtk-ok</property>
|
||||||
|
<property name="use_stock">True</property>
|
||||||
|
<property name="response_id">-5</property>
|
||||||
|
</widget>
|
||||||
|
<packing>
|
||||||
|
<property name="position">1</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
</widget>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="pack_type">GTK_PACK_END</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
</widget>
|
||||||
|
</child>
|
||||||
|
</widget>
|
||||||
|
<widget class="GtkDialog" id="statDialog">
|
||||||
|
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||||
|
<property name="border_width">5</property>
|
||||||
|
<property name="title" translatable="yes">pyGTKtalog - stats</property>
|
||||||
|
<property name="window_position">GTK_WIN_POS_CENTER_ON_PARENT</property>
|
||||||
|
<property name="type_hint">GDK_WINDOW_TYPE_HINT_DIALOG</property>
|
||||||
|
<property name="has_separator">False</property>
|
||||||
|
<child internal-child="vbox">
|
||||||
|
<widget class="GtkVBox" id="dialog-vbox3">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||||
|
<property name="spacing">2</property>
|
||||||
|
<child>
|
||||||
|
<widget class="GtkTable" id="table2">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||||
|
<property name="n_rows">4</property>
|
||||||
|
<property name="n_columns">2</property>
|
||||||
|
<property name="column_spacing">2</property>
|
||||||
|
<child>
|
||||||
|
<widget class="GtkLabel" id="size_label">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||||
|
<property name="label" translatable="yes">Total size:</property>
|
||||||
|
<property name="justify">GTK_JUSTIFY_RIGHT</property>
|
||||||
|
</widget>
|
||||||
|
<packing>
|
||||||
|
<property name="top_attach">3</property>
|
||||||
|
<property name="bottom_attach">4</property>
|
||||||
|
<property name="y_options">GTK_FILL</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<widget class="GtkLabel" id="files_label">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||||
|
<property name="label" translatable="yes">Number of files:</property>
|
||||||
|
<property name="justify">GTK_JUSTIFY_RIGHT</property>
|
||||||
|
</widget>
|
||||||
|
<packing>
|
||||||
|
<property name="top_attach">2</property>
|
||||||
|
<property name="bottom_attach">3</property>
|
||||||
|
<property name="y_options">GTK_FILL</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<widget class="GtkLabel" id="dirs_label">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||||
|
<property name="label" translatable="yes">Number of directories:</property>
|
||||||
|
<property name="justify">GTK_JUSTIFY_RIGHT</property>
|
||||||
|
</widget>
|
||||||
|
<packing>
|
||||||
|
<property name="top_attach">1</property>
|
||||||
|
<property name="bottom_attach">2</property>
|
||||||
|
<property name="y_options">GTK_FILL</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<widget class="GtkLabel" id="discs_label">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||||
|
<property name="label" translatable="yes">Number of discs:</property>
|
||||||
|
<property name="justify">GTK_JUSTIFY_RIGHT</property>
|
||||||
|
</widget>
|
||||||
|
<packing>
|
||||||
|
<property name="y_options">GTK_FILL</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<widget class="GtkEntry" id="size_entry">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||||
|
<property name="editable">False</property>
|
||||||
|
<property name="shadow_type">GTK_SHADOW_ETCHED_IN</property>
|
||||||
|
</widget>
|
||||||
|
<packing>
|
||||||
|
<property name="left_attach">1</property>
|
||||||
|
<property name="right_attach">2</property>
|
||||||
|
<property name="top_attach">3</property>
|
||||||
|
<property name="bottom_attach">4</property>
|
||||||
|
<property name="y_options">GTK_FILL</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<widget class="GtkEntry" id="files_entry">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||||
|
<property name="editable">False</property>
|
||||||
|
<property name="shadow_type">GTK_SHADOW_ETCHED_IN</property>
|
||||||
|
</widget>
|
||||||
|
<packing>
|
||||||
|
<property name="left_attach">1</property>
|
||||||
|
<property name="right_attach">2</property>
|
||||||
|
<property name="top_attach">2</property>
|
||||||
|
<property name="bottom_attach">3</property>
|
||||||
|
<property name="y_options">GTK_FILL</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<widget class="GtkEntry" id="dirs_entry">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||||
|
<property name="editable">False</property>
|
||||||
|
<property name="shadow_type">GTK_SHADOW_ETCHED_IN</property>
|
||||||
|
</widget>
|
||||||
|
<packing>
|
||||||
|
<property name="left_attach">1</property>
|
||||||
|
<property name="right_attach">2</property>
|
||||||
|
<property name="top_attach">1</property>
|
||||||
|
<property name="bottom_attach">2</property>
|
||||||
|
<property name="y_options">GTK_FILL</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<widget class="GtkEntry" id="discs_entry">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||||
|
<property name="editable">False</property>
|
||||||
|
<property name="shadow_type">GTK_SHADOW_ETCHED_IN</property>
|
||||||
|
</widget>
|
||||||
|
<packing>
|
||||||
|
<property name="left_attach">1</property>
|
||||||
|
<property name="right_attach">2</property>
|
||||||
|
<property name="y_options">GTK_FILL</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
</widget>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="position">1</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child internal-child="action_area">
|
||||||
|
<widget class="GtkHButtonBox" id="dialog-action_area3">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||||
|
<property name="layout_style">GTK_BUTTONBOX_END</property>
|
||||||
|
<child>
|
||||||
|
<widget class="GtkButton" id="button1">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="receives_default">True</property>
|
||||||
|
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||||
|
<property name="label" translatable="yes">gtk-close</property>
|
||||||
|
<property name="use_stock">True</property>
|
||||||
|
<property name="response_id">0</property>
|
||||||
|
</widget>
|
||||||
|
</child>
|
||||||
|
</widget>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="pack_type">GTK_PACK_END</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
</widget>
|
||||||
|
</child>
|
||||||
|
</widget>
|
||||||
|
<widget class="GtkDialog" id="renameDialog">
|
||||||
|
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||||
|
<property name="border_width">5</property>
|
||||||
|
<property name="title" translatable="yes">pyGTKtalog - rename</property>
|
||||||
|
<property name="window_position">GTK_WIN_POS_CENTER_ON_PARENT</property>
|
||||||
|
<property name="type_hint">GDK_WINDOW_TYPE_HINT_DIALOG</property>
|
||||||
|
<child internal-child="vbox">
|
||||||
|
<widget class="GtkVBox" id="dialog-vbox4">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||||
|
<property name="spacing">2</property>
|
||||||
|
<child>
|
||||||
|
<widget class="GtkHBox" id="hbox3">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<child>
|
||||||
|
<widget class="GtkLabel" id="label3">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="xpad">3</property>
|
||||||
|
<property name="label" translatable="yes">Rename</property>
|
||||||
|
</widget>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="fill">False</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<widget class="GtkEntry" id="name">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="can_default">True</property>
|
||||||
|
<property name="activates_default">True</property>
|
||||||
|
</widget>
|
||||||
|
<packing>
|
||||||
|
<property name="position">1</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
</widget>
|
||||||
|
<packing>
|
||||||
|
<property name="position">2</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child internal-child="action_area">
|
||||||
|
<widget class="GtkHButtonBox" id="dialog-action_area4">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||||
|
<property name="layout_style">GTK_BUTTONBOX_END</property>
|
||||||
|
<child>
|
||||||
|
<widget class="GtkButton" id="button2">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="receives_default">True</property>
|
||||||
|
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||||
|
<property name="label" translatable="yes">gtk-cancel</property>
|
||||||
|
<property name="use_stock">True</property>
|
||||||
|
<property name="response_id">-6</property>
|
||||||
|
</widget>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<widget class="GtkButton" id="button3">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="has_focus">True</property>
|
||||||
|
<property name="can_default">True</property>
|
||||||
|
<property name="has_default">True</property>
|
||||||
|
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||||
|
<property name="label" translatable="yes">gtk-ok</property>
|
||||||
|
<property name="use_stock">True</property>
|
||||||
|
<property name="response_id">-5</property>
|
||||||
|
</widget>
|
||||||
|
<packing>
|
||||||
|
<property name="position">1</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
</widget>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="pack_type">GTK_PACK_END</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
</widget>
|
||||||
|
</child>
|
||||||
|
</widget>
|
||||||
|
<widget class="GtkDialog" id="file_editDialog">
|
||||||
|
<property name="width_request">500</property>
|
||||||
|
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||||
|
<property name="border_width">5</property>
|
||||||
|
<property name="window_position">GTK_WIN_POS_CENTER_ON_PARENT</property>
|
||||||
|
<property name="type_hint">GDK_WINDOW_TYPE_HINT_DIALOG</property>
|
||||||
|
<property name="has_separator">False</property>
|
||||||
|
<child internal-child="vbox">
|
||||||
|
<widget class="GtkVBox" id="dialog-vbox5">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||||
|
<property name="spacing">2</property>
|
||||||
|
<child>
|
||||||
|
<widget class="GtkVBox" id="vbox2">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||||
|
<child>
|
||||||
|
<widget class="GtkFrame" id="frame1">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||||
|
<property name="label_xalign">0</property>
|
||||||
|
<child>
|
||||||
|
<widget class="GtkAlignment" id="alignment1">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||||
|
<property name="top_padding">3</property>
|
||||||
|
<property name="bottom_padding">3</property>
|
||||||
|
<property name="left_padding">12</property>
|
||||||
|
<property name="right_padding">3</property>
|
||||||
|
<child>
|
||||||
|
<widget class="GtkEntry" id="filename_entry">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||||
|
</widget>
|
||||||
|
</child>
|
||||||
|
</widget>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<widget class="GtkLabel" id="label2">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||||
|
<property name="label" translatable="yes"><b>Filename</b></property>
|
||||||
|
<property name="use_markup">True</property>
|
||||||
|
</widget>
|
||||||
|
<packing>
|
||||||
|
<property name="type">label_item</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
</widget>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<widget class="GtkFrame" id="frame3">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||||
|
<property name="label_xalign">0</property>
|
||||||
|
<child>
|
||||||
|
<widget class="GtkAlignment" id="alignment3">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||||
|
<property name="top_padding">3</property>
|
||||||
|
<property name="bottom_padding">3</property>
|
||||||
|
<property name="left_padding">12</property>
|
||||||
|
<property name="right_padding">3</property>
|
||||||
|
<child>
|
||||||
|
<widget class="GtkScrolledWindow" id="scrolledwindow1">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||||
|
<property name="hscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
|
||||||
|
<property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
|
||||||
|
<property name="shadow_type">GTK_SHADOW_IN</property>
|
||||||
|
<child>
|
||||||
|
<widget class="GtkTextView" id="description_text">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||||
|
</widget>
|
||||||
|
</child>
|
||||||
|
</widget>
|
||||||
|
</child>
|
||||||
|
</widget>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<widget class="GtkLabel" id="label5">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||||
|
<property name="label" translatable="yes"><b>Description</b></property>
|
||||||
|
<property name="use_markup">True</property>
|
||||||
|
</widget>
|
||||||
|
<packing>
|
||||||
|
<property name="type">label_item</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
</widget>
|
||||||
|
<packing>
|
||||||
|
<property name="position">1</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<widget class="GtkFrame" id="frame4">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||||
|
<property name="label_xalign">0</property>
|
||||||
|
<child>
|
||||||
|
<widget class="GtkAlignment" id="alignment4">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||||
|
<property name="top_padding">3</property>
|
||||||
|
<property name="bottom_padding">3</property>
|
||||||
|
<property name="left_padding">12</property>
|
||||||
|
<property name="right_padding">3</property>
|
||||||
|
<child>
|
||||||
|
<widget class="GtkScrolledWindow" id="scrolledwindow2">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||||
|
<property name="hscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
|
||||||
|
<property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
|
||||||
|
<property name="shadow_type">GTK_SHADOW_IN</property>
|
||||||
|
<child>
|
||||||
|
<widget class="GtkTextView" id="note_text">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||||
|
</widget>
|
||||||
|
</child>
|
||||||
|
</widget>
|
||||||
|
</child>
|
||||||
|
</widget>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<widget class="GtkLabel" id="label8">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||||
|
<property name="label" translatable="yes"><b>Note</b></property>
|
||||||
|
<property name="use_markup">True</property>
|
||||||
|
</widget>
|
||||||
|
<packing>
|
||||||
|
<property name="type">label_item</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
</widget>
|
||||||
|
<packing>
|
||||||
|
<property name="position">2</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
</widget>
|
||||||
|
<packing>
|
||||||
|
<property name="position">2</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child internal-child="action_area">
|
||||||
|
<widget class="GtkHButtonBox" id="dialog-action_area5">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||||
|
<property name="layout_style">GTK_BUTTONBOX_END</property>
|
||||||
|
<child>
|
||||||
|
<widget class="GtkButton" id="cancel">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="receives_default">True</property>
|
||||||
|
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||||
|
<property name="label" translatable="yes">gtk-cancel</property>
|
||||||
|
<property name="use_stock">True</property>
|
||||||
|
<property name="response_id">-6</property>
|
||||||
|
</widget>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<widget class="GtkButton" id="ok">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="receives_default">True</property>
|
||||||
|
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||||
|
<property name="label" translatable="yes">gtk-save</property>
|
||||||
|
<property name="use_stock">True</property>
|
||||||
|
<property name="response_id">-5</property>
|
||||||
|
</widget>
|
||||||
|
<packing>
|
||||||
|
<property name="position">1</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
</widget>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="pack_type">GTK_PACK_END</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
</widget>
|
||||||
|
</child>
|
||||||
|
</widget>
|
||||||
|
<widget class="GtkDialog" id="tagsDialog">
|
||||||
|
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||||
|
<property name="border_width">5</property>
|
||||||
|
<property name="window_position">GTK_WIN_POS_CENTER_ON_PARENT</property>
|
||||||
|
<property name="type_hint">GDK_WINDOW_TYPE_HINT_DIALOG</property>
|
||||||
|
<property name="has_separator">False</property>
|
||||||
|
<child internal-child="vbox">
|
||||||
|
<widget class="GtkVBox" id="dialog-vbox6">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||||
|
<property name="spacing">2</property>
|
||||||
|
<child>
|
||||||
|
<widget class="GtkVBox" id="vbox3">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||||
|
<child>
|
||||||
|
<widget class="GtkLabel" id="label9">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||||
|
<property name="label" translatable="yes">Fill field below with comma separated keywords you will desired to tag selected files.</property>
|
||||||
|
<property name="wrap">True</property>
|
||||||
|
</widget>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="padding">3</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<widget class="GtkEntry" id="tag_entry1">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="has_focus">True</property>
|
||||||
|
<property name="is_focus">True</property>
|
||||||
|
<property name="can_default">True</property>
|
||||||
|
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||||
|
<property name="activates_default">True</property>
|
||||||
|
</widget>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="position">1</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
</widget>
|
||||||
|
<packing>
|
||||||
|
<property name="position">1</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child internal-child="action_area">
|
||||||
|
<widget class="GtkHButtonBox" id="dialog-action_area6">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||||
|
<property name="layout_style">GTK_BUTTONBOX_END</property>
|
||||||
|
<child>
|
||||||
|
<widget class="GtkButton" id="cancel4">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="receives_default">True</property>
|
||||||
|
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||||
|
<property name="label" translatable="yes">gtk-cancel</property>
|
||||||
|
<property name="use_stock">True</property>
|
||||||
|
<property name="response_id">-6</property>
|
||||||
|
</widget>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<widget class="GtkButton" id="button5">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="can_default">True</property>
|
||||||
|
<property name="has_default">True</property>
|
||||||
|
<property name="receives_default">True</property>
|
||||||
|
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||||
|
<property name="label" translatable="yes">gtk-ok</property>
|
||||||
|
<property name="use_stock">True</property>
|
||||||
|
<property name="response_id">-5</property>
|
||||||
|
</widget>
|
||||||
|
<packing>
|
||||||
|
<property name="position">1</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
</widget>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="pack_type">GTK_PACK_END</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
</widget>
|
||||||
|
</child>
|
||||||
|
</widget>
|
||||||
|
<widget class="GtkDialog" id="tagRemove">
|
||||||
|
<property name="width_request">600</property>
|
||||||
|
<property name="height_request">400</property>
|
||||||
|
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||||
|
<property name="border_width">5</property>
|
||||||
|
<property name="title" translatable="yes">pyGTKtalog - remove tags</property>
|
||||||
|
<property name="modal">True</property>
|
||||||
|
<property name="window_position">GTK_WIN_POS_CENTER_ON_PARENT</property>
|
||||||
|
<property name="type_hint">GDK_WINDOW_TYPE_HINT_DIALOG</property>
|
||||||
|
<property name="has_separator">False</property>
|
||||||
|
<child internal-child="vbox">
|
||||||
|
<widget class="GtkVBox" id="dialog-vbox7">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||||
|
<property name="spacing">2</property>
|
||||||
|
<child>
|
||||||
|
<widget class="GtkScrolledWindow" id="scrolledwindow3">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||||
|
<property name="hscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
|
||||||
|
<property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
|
||||||
|
<property name="shadow_type">GTK_SHADOW_IN</property>
|
||||||
|
<child>
|
||||||
|
<widget class="GtkTreeView" id="treeview1">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||||
|
<property name="headers_clickable">True</property>
|
||||||
|
<property name="rules_hint">True</property>
|
||||||
|
</widget>
|
||||||
|
</child>
|
||||||
|
</widget>
|
||||||
|
<packing>
|
||||||
|
<property name="position">1</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child internal-child="action_area">
|
||||||
|
<widget class="GtkHButtonBox" id="dialog-action_area7">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||||
|
<property name="layout_style">GTK_BUTTONBOX_END</property>
|
||||||
|
<child>
|
||||||
|
<widget class="GtkButton" id="cancel5">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="receives_default">True</property>
|
||||||
|
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||||
|
<property name="label" translatable="yes">gtk-cancel</property>
|
||||||
|
<property name="use_stock">True</property>
|
||||||
|
<property name="response_id">-6</property>
|
||||||
|
</widget>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<widget class="GtkButton" id="ok2">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="receives_default">True</property>
|
||||||
|
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||||
|
<property name="label" translatable="yes">gtk-remove</property>
|
||||||
|
<property name="use_stock">True</property>
|
||||||
|
<property name="response_id">-5</property>
|
||||||
|
</widget>
|
||||||
|
<packing>
|
||||||
|
<property name="position">1</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
</widget>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="pack_type">GTK_PACK_END</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
</widget>
|
||||||
|
</child>
|
||||||
|
</widget>
|
||||||
|
</glade-interface>
|
||||||
1083
resources/glade/main.glade
Normal file
276
resources/glade/search.glade
Normal file
@@ -0,0 +1,276 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!DOCTYPE glade-interface SYSTEM "glade-2.0.dtd">
|
||||||
|
<!--Generated with glade3 3.4.0 on Tue May 13 10:23:51 2008 -->
|
||||||
|
<glade-interface>
|
||||||
|
<widget class="GtkWindow" id="search_window">
|
||||||
|
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||||
|
<property name="border_width">3</property>
|
||||||
|
<signal name="delete_event" handler="on_search_window_delete_event"/>
|
||||||
|
<child>
|
||||||
|
<widget class="GtkVBox" id="vbox3">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||||
|
<child>
|
||||||
|
<widget class="GtkHBox" id="hbox3">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||||
|
<property name="spacing">3</property>
|
||||||
|
<child>
|
||||||
|
<widget class="GtkLabel" id="label5">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||||
|
<property name="label" translatable="yes">Search for:</property>
|
||||||
|
</widget>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<widget class="GtkComboBoxEntry" id="comboboxentry">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||||
|
<child internal-child="entry">
|
||||||
|
<widget class="GtkEntry" id="search_entry">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||||
|
<signal name="activate" handler="on_search_activate"/>
|
||||||
|
</widget>
|
||||||
|
</child>
|
||||||
|
</widget>
|
||||||
|
<packing>
|
||||||
|
<property name="position">1</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<widget class="GtkHButtonBox" id="hbuttonbox2">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||||
|
<child>
|
||||||
|
<widget class="GtkButton" id="search_button">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="receives_default">True</property>
|
||||||
|
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||||
|
<property name="label" translatable="yes">gtk-find</property>
|
||||||
|
<property name="use_stock">True</property>
|
||||||
|
<property name="response_id">1</property>
|
||||||
|
<signal name="clicked" handler="on_search_activate"/>
|
||||||
|
</widget>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
</widget>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="position">2</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
</widget>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<widget class="GtkFrame" id="frame3">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||||
|
<property name="label_xalign">0</property>
|
||||||
|
<child>
|
||||||
|
<widget class="GtkAlignment" id="alignment3">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||||
|
<property name="top_padding">3</property>
|
||||||
|
<property name="bottom_padding">3</property>
|
||||||
|
<property name="left_padding">12</property>
|
||||||
|
<property name="right_padding">3</property>
|
||||||
|
<child>
|
||||||
|
<widget class="GtkVBox" id="vbox1">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||||
|
<child>
|
||||||
|
<widget class="GtkScrolledWindow" id="scrolledwindow3">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||||
|
<property name="hscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
|
||||||
|
<property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
|
||||||
|
<property name="shadow_type">GTK_SHADOW_IN</property>
|
||||||
|
<child>
|
||||||
|
<widget class="GtkTreeView" id="result">
|
||||||
|
<property name="width_request">600</property>
|
||||||
|
<property name="height_request">300</property>
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||||
|
<property name="headers_clickable">True</property>
|
||||||
|
<property name="rules_hint">True</property>
|
||||||
|
<signal name="row_activated" handler="on_result_row_activated"/>
|
||||||
|
<signal name="button_release_event" handler="on_result_button_release_event"/>
|
||||||
|
</widget>
|
||||||
|
</child>
|
||||||
|
</widget>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<widget class="GtkStatusbar" id="statusbar">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||||
|
<property name="spacing">2</property>
|
||||||
|
<property name="has_resize_grip">False</property>
|
||||||
|
</widget>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="position">1</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
</widget>
|
||||||
|
</child>
|
||||||
|
</widget>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<widget class="GtkLabel" id="label6">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||||
|
<property name="label" translatable="yes"><b>Search results</b></property>
|
||||||
|
<property name="use_markup">True</property>
|
||||||
|
</widget>
|
||||||
|
<packing>
|
||||||
|
<property name="type">label_item</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
</widget>
|
||||||
|
<packing>
|
||||||
|
<property name="position">1</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<widget class="GtkHButtonBox" id="hbuttonbox1">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||||
|
<property name="border_width">3</property>
|
||||||
|
<property name="layout_style">GTK_BUTTONBOX_END</property>
|
||||||
|
<child>
|
||||||
|
<widget class="GtkButton" id="close">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="receives_default">True</property>
|
||||||
|
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||||
|
<property name="label" translatable="yes">gtk-close</property>
|
||||||
|
<property name="use_stock">True</property>
|
||||||
|
<property name="response_id">-6</property>
|
||||||
|
<signal name="clicked" handler="on_close_clicked"/>
|
||||||
|
</widget>
|
||||||
|
</child>
|
||||||
|
</widget>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="position">2</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
</widget>
|
||||||
|
</child>
|
||||||
|
</widget>
|
||||||
|
<widget class="GtkMenu" id="files_popup">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||||
|
<child>
|
||||||
|
<widget class="GtkMenuItem" id="add_tag">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||||
|
<property name="label" translatable="yes">_Add tag</property>
|
||||||
|
<property name="use_underline">True</property>
|
||||||
|
<signal name="activate" handler="on_add_tag_activate"/>
|
||||||
|
</widget>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<widget class="GtkMenuItem" id="delete_tag">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||||
|
<property name="label" translatable="yes">Remo_ve tag</property>
|
||||||
|
<property name="use_underline">True</property>
|
||||||
|
<signal name="activate" handler="on_delete_tag_activate"/>
|
||||||
|
</widget>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<widget class="GtkSeparatorMenuItem" id="separator16">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
</widget>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<widget class="GtkMenuItem" id="add_thumb">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||||
|
<property name="label" translatable="yes">Add _Thumbnail</property>
|
||||||
|
<property name="use_underline">True</property>
|
||||||
|
<signal name="activate" handler="on_add_thumb_activate"/>
|
||||||
|
</widget>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<widget class="GtkMenuItem" id="remove_thumb">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||||
|
<property name="label" translatable="yes">Re_move Thumbnail</property>
|
||||||
|
<property name="use_underline">True</property>
|
||||||
|
<signal name="activate" handler="on_remove_thumb_activate"/>
|
||||||
|
</widget>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<widget class="GtkSeparatorMenuItem" id="separator17">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
</widget>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<widget class="GtkMenuItem" id="add_image">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||||
|
<property name="tooltip" translatable="yes">Add images to file. If file have no thumbnail,
|
||||||
|
thumbnail from first image will be generated.</property>
|
||||||
|
<property name="label" translatable="yes">Add _Images</property>
|
||||||
|
<property name="use_underline">True</property>
|
||||||
|
<signal name="activate" handler="on_add_image_activate"/>
|
||||||
|
</widget>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<widget class="GtkMenuItem" id="remove_image">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||||
|
<property name="label" translatable="yes">Rem_ove All Images</property>
|
||||||
|
<property name="use_underline">True</property>
|
||||||
|
<signal name="activate" handler="on_remove_image_activate"/>
|
||||||
|
</widget>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<widget class="GtkSeparatorMenuItem" id="separator18">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
</widget>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<widget class="GtkMenuItem" id="edit">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||||
|
<property name="label" translatable="yes">_Edit</property>
|
||||||
|
<property name="use_underline">True</property>
|
||||||
|
<signal name="activate" handler="on_edit_activate"/>
|
||||||
|
</widget>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<widget class="GtkMenuItem" id="delete">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||||
|
<property name="label" translatable="yes">_Delete</property>
|
||||||
|
<property name="use_underline">True</property>
|
||||||
|
<signal name="activate" handler="on_delete_activate"/>
|
||||||
|
</widget>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<widget class="GtkMenuItem" id="rename">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||||
|
<property name="label" translatable="yes">_Rename</property>
|
||||||
|
<property name="use_underline">True</property>
|
||||||
|
<signal name="activate" handler="on_rename_activate"/>
|
||||||
|
</widget>
|
||||||
|
</child>
|
||||||
|
</widget>
|
||||||
|
</glade-interface>
|
||||||
4
runtests.sh
Executable file
@@ -0,0 +1,4 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# run unittests
|
||||||
|
cd src/test
|
||||||
|
python run_tests.py
|
||||||
25
src/__init__.py
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
# This Python file uses the following encoding: utf-8
|
||||||
|
#
|
||||||
|
# Author: Roman 'gryf' Dobosz gryf@elysium.pl
|
||||||
|
#
|
||||||
|
# Copyright (C) 2007 by Roman 'gryf' Dobosz
|
||||||
|
#
|
||||||
|
# This file is part of pyGTKtalog.
|
||||||
|
#
|
||||||
|
# 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
|
||||||
|
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
25
src/ctrls/__init__.py
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
# This Python file uses the following encoding: utf-8
|
||||||
|
#
|
||||||
|
# Author: Roman 'gryf' Dobosz gryf@elysium.pl
|
||||||
|
#
|
||||||
|
# Copyright (C) 2007 by Roman 'gryf' Dobosz
|
||||||
|
#
|
||||||
|
# This file is part of pyGTKtalog.
|
||||||
|
#
|
||||||
|
# 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
|
||||||
|
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
282
src/ctrls/c_config.py
Normal file
@@ -0,0 +1,282 @@
|
|||||||
|
# This Python file uses the following encoding: utf-8
|
||||||
|
#
|
||||||
|
# Author: Roman 'gryf' Dobosz gryf@elysium.pl
|
||||||
|
#
|
||||||
|
# Copyright (C) 2007 by Roman 'gryf' Dobosz
|
||||||
|
#
|
||||||
|
# This file is part of pyGTKtalog.
|
||||||
|
#
|
||||||
|
# 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
|
||||||
|
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
from gtkmvc import Controller
|
||||||
|
import views.v_dialogs as Dialogs
|
||||||
|
|
||||||
|
import gtk
|
||||||
|
|
||||||
|
class ConfigController(Controller):
|
||||||
|
category_dict = {
|
||||||
|
'Disk options':'disk_group',
|
||||||
|
'General':'general_group',
|
||||||
|
'Scan options':'scan_group',
|
||||||
|
'Files extensions':'ft_group',
|
||||||
|
}
|
||||||
|
category_order = ['General', 'Disk options', 'Scan options',
|
||||||
|
'Files extensions',]
|
||||||
|
|
||||||
|
def __init__(self, model):
|
||||||
|
Controller.__init__(self, model)
|
||||||
|
return
|
||||||
|
|
||||||
|
def register_view(self, view):
|
||||||
|
Controller.register_view(self, view)
|
||||||
|
|
||||||
|
# get data from Config object and put it into view
|
||||||
|
self.view['mnt_entry'].set_text(self.model.confd['cd'])
|
||||||
|
self.view['ejt_entry'].set_text(self.model.confd['ejectapp'])
|
||||||
|
self.view['ch_win'].set_active(self.model.confd['savewin'])
|
||||||
|
self.view['ch_pan'].set_active(self.model.confd['savepan'])
|
||||||
|
self.view['ch_eject'].set_active(self.model.confd['eject'])
|
||||||
|
self.view['ch_xls'].set_active(self.model.confd['exportxls'])
|
||||||
|
self.view['ch_quit'].set_active(self.model.confd['confirmquit'])
|
||||||
|
self.view['ch_wrnmount'].set_active(self.model.confd['mntwarn'])
|
||||||
|
self.view['ch_wrndel'].set_active(self.model.confd['delwarn'])
|
||||||
|
self.view['ch_warnnew'].set_active(self.model.confd['confirmabandon'])
|
||||||
|
self.view['ch_thumb'].set_active(self.model.confd['thumbs'])
|
||||||
|
self.view['ch_exif'].set_active(self.model.confd['exif'])
|
||||||
|
self.view['ch_gthumb'].set_active(self.model.confd['gthumb'])
|
||||||
|
self.view['ch_compress'].set_active(self.model.confd['compress'])
|
||||||
|
self.view['ch_retrive'].set_active(self.model.confd['retrive'])
|
||||||
|
self.view['ch_imageviewer'].set_active(self.model.confd['imgview'])
|
||||||
|
self.view['entry_imv'].set_text(self.model.confd['imgprog'])
|
||||||
|
|
||||||
|
self.__toggle_scan_group()
|
||||||
|
|
||||||
|
# initialize tree view
|
||||||
|
self.__setup_category_tree()
|
||||||
|
|
||||||
|
# initialize models for files extensions
|
||||||
|
vi = self.view['extension_tree']
|
||||||
|
vi.get_selection().set_mode(gtk.SELECTION_MULTIPLE)
|
||||||
|
#self.view['extension_tree'].set_model(self.model.ext_tree)
|
||||||
|
self.__setup_extension_tree()
|
||||||
|
|
||||||
|
self.view['config'].show()
|
||||||
|
return
|
||||||
|
|
||||||
|
#################
|
||||||
|
# connect signals
|
||||||
|
def on_extension_tree_cursor_changed(self, tree):
|
||||||
|
model = tree.get_model()
|
||||||
|
selected = model.get_value(model.get_iter(tree.get_cursor()[0]), 0)
|
||||||
|
ext = self.model.confd['extensions']
|
||||||
|
self.view['ext_entry'].set_text(selected)
|
||||||
|
self.view['com_entry'].set_text(ext[selected])
|
||||||
|
|
||||||
|
def on_category_tree_cursor_changed(self, tree):
|
||||||
|
"""change view to selected row corresponding to group of properties"""
|
||||||
|
model = tree.get_model()
|
||||||
|
selected = model.get_value(model.get_iter(tree.get_cursor()[0]), 0)
|
||||||
|
iterator = tree.get_model().get_iter_first();
|
||||||
|
while iterator != None:
|
||||||
|
if model.get_value(iterator, 0) == selected:
|
||||||
|
self.view[self.category_dict[model.get_value(iterator,
|
||||||
|
0)]].show()
|
||||||
|
self.view['desc'].set_markup("<b>%s</b>" % selected)
|
||||||
|
else:
|
||||||
|
self.view[self.category_dict[model.get_value(iterator,
|
||||||
|
0)]].hide()
|
||||||
|
iterator = tree.get_model().iter_next(iterator);
|
||||||
|
return
|
||||||
|
|
||||||
|
def on_cancelbutton_clicked(self, button):
|
||||||
|
self.view['config'].destroy()
|
||||||
|
return
|
||||||
|
|
||||||
|
def on_okbutton_clicked(self, button):
|
||||||
|
# get data from view and put it into Config object
|
||||||
|
self.model.confd['cd'] = self.view['mnt_entry'].get_text()
|
||||||
|
self.model.confd['ejectapp'] = self.view['ejt_entry'].get_text()
|
||||||
|
self.model.confd['savewin'] = self.view['ch_win'].get_active()
|
||||||
|
self.model.confd['savepan'] = self.view['ch_pan'].get_active()
|
||||||
|
self.model.confd['eject'] = self.view['ch_eject'].get_active()
|
||||||
|
self.model.confd['exportxls'] = self.view['ch_xls'].get_active()
|
||||||
|
self.model.confd['confirmquit'] = self.view['ch_quit'].get_active()
|
||||||
|
self.model.confd['mntwarn'] = self.view['ch_wrnmount'].get_active()
|
||||||
|
self.model.confd['delwarn'] = self.view['ch_wrndel'].get_active()
|
||||||
|
v = self.view['ch_warnnew']
|
||||||
|
self.model.confd['confirmabandon'] = v.get_active()
|
||||||
|
self.model.confd['thumbs'] = self.view['ch_thumb'].get_active()
|
||||||
|
self.model.confd['exif'] = self.view['ch_exif'].get_active()
|
||||||
|
self.model.confd['gthumb'] = self.view['ch_gthumb'].get_active()
|
||||||
|
self.model.confd['compress'] = self.view['ch_compress'].get_active()
|
||||||
|
self.model.confd['retrive'] = self.view['ch_retrive'].get_active()
|
||||||
|
self.model.confd['imgview'] = self.view['ch_imageviewer'].get_active()
|
||||||
|
self.model.confd['imgprog'] = self.view['entry_imv'].get_text()
|
||||||
|
self.model.save()
|
||||||
|
self.view['config'].destroy()
|
||||||
|
|
||||||
|
def on_button_ejt_clicked(self, button):
|
||||||
|
fn = self.__show_filechooser("Choose eject program")
|
||||||
|
self.view['ejt_entryentry_imv'].set_text(fn)
|
||||||
|
|
||||||
|
def on_button_mnt_clicked(self, button):
|
||||||
|
fn = self.__show_filechooser("Choose mount point")
|
||||||
|
self.view['mnt_entry'].set_text(fn)
|
||||||
|
|
||||||
|
def on_ch_retrive_toggled(self, widget):
|
||||||
|
self.__toggle_scan_group()
|
||||||
|
|
||||||
|
def on_ch_imageviewer_toggled(self, checkbox):
|
||||||
|
state = self.view['ch_imageviewer'].get_active()
|
||||||
|
for i in ['label_imv', 'entry_imv', 'button_imv']:
|
||||||
|
self.view[i].set_sensitive(state)
|
||||||
|
|
||||||
|
def on_button_imv_clicked(self, widget):
|
||||||
|
fn = self.__show_filechooser("Choose image viewer")
|
||||||
|
self.view['entry_imv'].set_text(fn)
|
||||||
|
|
||||||
|
def on_ext_add_clicked(self, widget):
|
||||||
|
ext = self.view['ext_entry'].get_text().lower()
|
||||||
|
com = self.view['com_entry'].get_text()
|
||||||
|
if len(ext) == 0 and len(com) == 0:
|
||||||
|
Dialogs.Err("Config - pyGTKtalog", "Error",
|
||||||
|
"Extension and command required")
|
||||||
|
return
|
||||||
|
|
||||||
|
if len(com) == 0:
|
||||||
|
Dialogs.Err("Config - pyGTKtalog", "Error", "Command is empty")
|
||||||
|
return
|
||||||
|
|
||||||
|
if len(ext) == 0:
|
||||||
|
Dialogs.Err("Config - pyGTKtalog", "Error", "Extension is empty")
|
||||||
|
return
|
||||||
|
|
||||||
|
if ext in self.model.confd['extensions'].keys():
|
||||||
|
obj = Dialogs.Qst('Alter extension',
|
||||||
|
'Alter extension?',
|
||||||
|
'Extension "%s" will be altered.' % ext)
|
||||||
|
if not obj.run():
|
||||||
|
return
|
||||||
|
self.model.confd['extensions'][ext] = com
|
||||||
|
|
||||||
|
self.__setup_extension_tree()
|
||||||
|
return
|
||||||
|
|
||||||
|
def on_ext_del_clicked(self, widget):
|
||||||
|
v = self.view['extension_tree']
|
||||||
|
model, selection = v.get_selection().get_selected_rows()
|
||||||
|
if len(selection) == 0:
|
||||||
|
Dialogs.Err("Config - pyGTKtalog", "Error", "No item selected")
|
||||||
|
return
|
||||||
|
elif len(selection) == 1:
|
||||||
|
sufix = ''
|
||||||
|
else:
|
||||||
|
sufix = "s"
|
||||||
|
|
||||||
|
if self.model.confd['delwarn']:
|
||||||
|
obj = Dialogs.Qst('Delete extension%s' % sufix,
|
||||||
|
'Delete extension%s?' % sufix,
|
||||||
|
'Object%s will be permanently removed.' % sufix)
|
||||||
|
if not obj.run():
|
||||||
|
return
|
||||||
|
|
||||||
|
for i in selection:
|
||||||
|
m = self.model.confd['extensions']
|
||||||
|
m.pop(model.get_value(model.get_iter(i), 0))
|
||||||
|
|
||||||
|
self.__setup_extension_tree()
|
||||||
|
return
|
||||||
|
|
||||||
|
############################
|
||||||
|
# private controller methods
|
||||||
|
def __setup_extension_tree(self):
|
||||||
|
self.model.refresh_ext()
|
||||||
|
|
||||||
|
self.view['extension_tree'].set_model(self.model.ext_tree)
|
||||||
|
|
||||||
|
for i in self.view['extension_tree'].get_columns():
|
||||||
|
self.view['extension_tree'].remove_column(i)
|
||||||
|
cell = gtk.CellRendererText()
|
||||||
|
column = gtk.TreeViewColumn("Extension", cell, text=0)
|
||||||
|
column.set_resizable(True)
|
||||||
|
self.view['extension_tree'].append_column(column)
|
||||||
|
|
||||||
|
column = gtk.TreeViewColumn("Command", cell, text=1)
|
||||||
|
column.set_resizable(True)
|
||||||
|
self.view['extension_tree'].append_column(column)
|
||||||
|
|
||||||
|
def __toggle_scan_group(self):
|
||||||
|
for i in ('ch_thumb','ch_exif','ch_gthumb'):
|
||||||
|
self.view[i].set_sensitive(self.view['ch_retrive'].get_active())
|
||||||
|
return
|
||||||
|
|
||||||
|
def __setup_category_tree(self):
|
||||||
|
category_tree = self.view['category_tree']
|
||||||
|
category_tree.set_model(self.model.category_tree)
|
||||||
|
|
||||||
|
self.model.category_tree.clear()
|
||||||
|
for i in self.category_order:
|
||||||
|
myiter = self.model.category_tree.insert_before(None,None)
|
||||||
|
self.model.category_tree.set_value(myiter,0,i)
|
||||||
|
|
||||||
|
cell = gtk.CellRendererText()
|
||||||
|
column = gtk.TreeViewColumn("Name",cell,text=0)
|
||||||
|
column.set_resizable(True)
|
||||||
|
category_tree.append_column(column)
|
||||||
|
|
||||||
|
def __show_filechooser(self, title):
|
||||||
|
"""dialog for choose eject"""
|
||||||
|
fn = None
|
||||||
|
dialog = gtk.FileChooserDialog(
|
||||||
|
title=title,
|
||||||
|
action=gtk.FILE_CHOOSER_ACTION_OPEN,
|
||||||
|
buttons=(gtk.STOCK_CANCEL,
|
||||||
|
gtk.RESPONSE_CANCEL,
|
||||||
|
gtk.STOCK_OPEN,
|
||||||
|
gtk.RESPONSE_OK))
|
||||||
|
|
||||||
|
dialog.set_default_response(gtk.RESPONSE_OK)
|
||||||
|
|
||||||
|
response = dialog.run()
|
||||||
|
if response == gtk.RESPONSE_OK:
|
||||||
|
if __debug__:
|
||||||
|
print "c_config.py: __show_filechooser()",
|
||||||
|
print dialog.get_filename()
|
||||||
|
fn = dialog.get_filename()
|
||||||
|
dialog.destroy()
|
||||||
|
return fn
|
||||||
|
|
||||||
|
def __show_dirchooser(self):
|
||||||
|
"""dialog for point the mountpoint"""
|
||||||
|
dialog = gtk.FileChooserDialog(
|
||||||
|
title="Choose mount point",
|
||||||
|
action=gtk.FILE_CHOOSER_ACTION_OPEN,
|
||||||
|
buttons=(gtk.STOCK_CANCEL,
|
||||||
|
gtk.RESPONSE_CANCEL,
|
||||||
|
gtk.STOCK_OPEN,
|
||||||
|
gtk.RESPONSE_OK))
|
||||||
|
|
||||||
|
dialog.set_action(gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER)
|
||||||
|
dialog.set_filename(self.view['mnt_entry'].get_text())
|
||||||
|
dialog.set_default_response(gtk.RESPONSE_OK)
|
||||||
|
|
||||||
|
response = dialog.run()
|
||||||
|
if response == gtk.RESPONSE_OK:
|
||||||
|
self.view['mnt_entry'].set_text(dialog.get_filename())
|
||||||
|
dialog.destroy()
|
||||||
|
|
||||||
|
pass # end of class
|
||||||
|
|
||||||
1700
src/ctrls/c_main.py
Normal file
379
src/ctrls/c_search.py
Normal file
@@ -0,0 +1,379 @@
|
|||||||
|
# This Python file uses the following encoding: utf-8
|
||||||
|
#
|
||||||
|
# Author: Roman 'gryf' Dobosz gryf@elysium.pl
|
||||||
|
#
|
||||||
|
# Copyright (C) 2007 by Roman 'gryf' Dobosz
|
||||||
|
#
|
||||||
|
# This file is part of pyGTKtalog.
|
||||||
|
#
|
||||||
|
# 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
|
||||||
|
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
|
||||||
|
import gtk
|
||||||
|
|
||||||
|
from gtkmvc import Controller
|
||||||
|
import views.v_dialogs as Dialogs
|
||||||
|
|
||||||
|
class SearchController(Controller):
|
||||||
|
"""Controller for main application window"""
|
||||||
|
|
||||||
|
def __init__(self, model):
|
||||||
|
"""Initialize controller"""
|
||||||
|
Controller.__init__(self, model)
|
||||||
|
self.search_string = ""
|
||||||
|
return
|
||||||
|
|
||||||
|
def register_view(self, view):
|
||||||
|
Controller.register_view(self, view)
|
||||||
|
|
||||||
|
# Setup TreeView result widget, as columned list
|
||||||
|
v = self.view['result']
|
||||||
|
v.set_model(self.model.search_list)
|
||||||
|
|
||||||
|
v.get_selection().set_mode(gtk.SELECTION_MULTIPLE)
|
||||||
|
|
||||||
|
c = gtk.TreeViewColumn('Disc', gtk.CellRendererText(), text=1)
|
||||||
|
c.set_sort_column_id(1)
|
||||||
|
c.set_resizable(True)
|
||||||
|
v.append_column(c)
|
||||||
|
|
||||||
|
c = gtk.TreeViewColumn('Filename')
|
||||||
|
cellpb = gtk.CellRendererPixbuf()
|
||||||
|
cell = gtk.CellRendererText()
|
||||||
|
c.pack_start(cellpb, False)
|
||||||
|
c.pack_start(cell, True)
|
||||||
|
c.set_attributes(cellpb, stock_id=7)
|
||||||
|
c.set_attributes(cell, text=2)
|
||||||
|
c.set_sort_column_id(2)
|
||||||
|
c.set_resizable(True)
|
||||||
|
v.append_column(c)
|
||||||
|
|
||||||
|
c = gtk.TreeViewColumn('Path', gtk.CellRendererText(), text=3)
|
||||||
|
c.set_sort_column_id(3)
|
||||||
|
c.set_resizable(True)
|
||||||
|
v.append_column(c)
|
||||||
|
|
||||||
|
c = gtk.TreeViewColumn('Size', gtk.CellRendererText(), text=4)
|
||||||
|
c.set_sort_column_id(4)
|
||||||
|
c.set_resizable(True)
|
||||||
|
v.append_column(c)
|
||||||
|
|
||||||
|
c = gtk.TreeViewColumn('Date', gtk.CellRendererText(), text=5)
|
||||||
|
c.set_sort_column_id(5)
|
||||||
|
c.set_resizable(True)
|
||||||
|
v.append_column(c)
|
||||||
|
v.set_search_column(2)
|
||||||
|
|
||||||
|
# combobox
|
||||||
|
self.view['comboboxentry'].set_model(self.model.search_history)
|
||||||
|
self.view['comboboxentry'].set_text_column(0)
|
||||||
|
# statusbar
|
||||||
|
self.context_id = self.view['statusbar'].get_context_id('search')
|
||||||
|
self.view['statusbar'].pop(self.context_id)
|
||||||
|
self.view['search_window'].show()
|
||||||
|
self.model.search_created = True;
|
||||||
|
return
|
||||||
|
|
||||||
|
#########################################################################
|
||||||
|
# Connect signals from GUI, like menu objects, toolbar buttons and so on.
|
||||||
|
def on_search_window_delete_event(self, window, event):
|
||||||
|
"""if window was closed, reset attributes"""
|
||||||
|
self.model.point = None
|
||||||
|
self.model.search_created = False;
|
||||||
|
return False
|
||||||
|
|
||||||
|
def on_close_clicked(self, button):
|
||||||
|
"""close search window"""
|
||||||
|
self.model.point = None
|
||||||
|
self.model.search_created = False;
|
||||||
|
self.view['search_window'].destroy()
|
||||||
|
|
||||||
|
def on_search_activate(self, entry):
|
||||||
|
"""find button or enter pressed on entry search. Do the search"""
|
||||||
|
search_txt = self.view['search_entry'].get_text()
|
||||||
|
self.search_string = search_txt
|
||||||
|
found = self.model.search(search_txt)
|
||||||
|
self.model.add_search_history(search_txt)
|
||||||
|
self.__set_status_bar(found)
|
||||||
|
|
||||||
|
def on_result_row_activated(self, treeview, path, treecolumn):
|
||||||
|
"""result treeview row activated, change models 'point' observable
|
||||||
|
variable to id of elected item. rest is all in main controler hands."""
|
||||||
|
model = treeview.get_model()
|
||||||
|
s_iter = model.get_iter(path)
|
||||||
|
self.model.point = model.get_value(s_iter, 0)
|
||||||
|
|
||||||
|
def on_result_button_release_event(self, tree, event):
|
||||||
|
if event.button == 3: # Right mouse button. Show context menu.
|
||||||
|
try:
|
||||||
|
selection = tree.get_selection()
|
||||||
|
model, list_of_paths = selection.get_selected_rows()
|
||||||
|
except TypeError:
|
||||||
|
list_of_paths = []
|
||||||
|
|
||||||
|
if len(list_of_paths) == 0:
|
||||||
|
# try to select item under cursor
|
||||||
|
try:
|
||||||
|
path, column, x, y = tree.get_path_at_pos(int(event.x),
|
||||||
|
int(event.y))
|
||||||
|
except TypeError:
|
||||||
|
# failed, do not show any popup and return
|
||||||
|
tree.get_selection().unselect_all()
|
||||||
|
return False
|
||||||
|
selection.select_path(path[0])
|
||||||
|
|
||||||
|
if len(list_of_paths) > 1:
|
||||||
|
self.view['add_image'].set_sensitive(False)
|
||||||
|
self.view['rename'].set_sensitive(False)
|
||||||
|
self.view['edit'].set_sensitive(False)
|
||||||
|
else:
|
||||||
|
self.view['add_image'].set_sensitive(True)
|
||||||
|
self.view['rename'].set_sensitive(True)
|
||||||
|
self.view['edit'].set_sensitive(True)
|
||||||
|
self.view['files_popup'].popup(None, None, None, 0, 0)
|
||||||
|
self.view['files_popup'].show_all()
|
||||||
|
return True
|
||||||
|
|
||||||
|
def on_add_tag_activate(self, menu_item):
|
||||||
|
"""Add tags to selected files"""
|
||||||
|
tags = Dialogs.TagsDialog().run()
|
||||||
|
if not tags:
|
||||||
|
return
|
||||||
|
ids = self.__get_tv_selection_ids(self.view['result'])
|
||||||
|
for item_id in ids:
|
||||||
|
self.model.add_tags(item_id, tags)
|
||||||
|
|
||||||
|
self.model.unsaved_project = True
|
||||||
|
return
|
||||||
|
|
||||||
|
def on_delete_tag_activate(self, menu_item):
|
||||||
|
ids = self.__get_tv_selection_ids(self.view['result'])
|
||||||
|
if not ids:
|
||||||
|
Dialogs.Inf("Remove tags", "No files selected",
|
||||||
|
"You have to select some files first.")
|
||||||
|
return
|
||||||
|
|
||||||
|
tags = self.model.get_tags_by_file_id(ids)
|
||||||
|
if tags:
|
||||||
|
d = Dialogs.TagsRemoveDialog(tags)
|
||||||
|
retcode, retval = d.run()
|
||||||
|
if retcode=="ok" and not retval:
|
||||||
|
Dialogs.Inf("Remove tags", "No tags selected",
|
||||||
|
"You have to select any tag to remove from files.")
|
||||||
|
return
|
||||||
|
elif retcode == "ok" and retval:
|
||||||
|
self.model.delete_tags(ids, retval)
|
||||||
|
found = self.model.search(self.search_string)
|
||||||
|
self.__set_status_bar(found)
|
||||||
|
|
||||||
|
def on_add_thumb_activate(self, menu_item):
|
||||||
|
image, only_thumbs = Dialogs.LoadImageFile().run()
|
||||||
|
if not image:
|
||||||
|
return
|
||||||
|
ids = self.__get_tv_selection_ids(self.view['result'])
|
||||||
|
for item_id in ids:
|
||||||
|
self.model.add_thumbnail(image, item_id)
|
||||||
|
|
||||||
|
self.model.unsaved_project = True
|
||||||
|
return
|
||||||
|
|
||||||
|
def on_remove_thumb_activate(self, menu_item):
|
||||||
|
if self.model.config.confd['delwarn']:
|
||||||
|
title = 'Delete thumbnails'
|
||||||
|
question = 'Delete thumbnails?'
|
||||||
|
description = "Thumbnails for selected items will be permanently"
|
||||||
|
description += " removed from catalog."
|
||||||
|
obj = Dialogs.Qst(title, question, description)
|
||||||
|
if not obj.run():
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
ids = self.__get_tv_selection_ids(self.view['result'])
|
||||||
|
for item_id in ids:
|
||||||
|
self.model.del_thumbnail(item_id)
|
||||||
|
except:
|
||||||
|
if __debug__:
|
||||||
|
print "c_search.py: on_remove_thumb_activate(): error on",
|
||||||
|
print "getting selected items or removing thumbnails"
|
||||||
|
return
|
||||||
|
|
||||||
|
self.model.unsaved_project = True
|
||||||
|
return
|
||||||
|
|
||||||
|
def on_add_image_activate(self, menu_item):
|
||||||
|
dialog = Dialogs.LoadImageFile(True)
|
||||||
|
msg = "Don't copy images. Generate only thumbnails."
|
||||||
|
toggle = gtk.CheckButton(msg)
|
||||||
|
toggle.show()
|
||||||
|
dialog.dialog.set_extra_widget(toggle)
|
||||||
|
|
||||||
|
images, only_thumbs = dialog.run()
|
||||||
|
if not images:
|
||||||
|
return
|
||||||
|
|
||||||
|
for image in images:
|
||||||
|
try:
|
||||||
|
selection = self.view['result'].get_selection()
|
||||||
|
model, list_of_paths = selection.get_selected_rows()
|
||||||
|
fid = model.get_value(model.get_iter(list_of_paths[0]), 0)
|
||||||
|
except:
|
||||||
|
try:
|
||||||
|
path, column = self.view['result'].get_cursor()
|
||||||
|
model = self.view['result'].get_model()
|
||||||
|
fiter = model.get_iter(path)
|
||||||
|
fid = model.get_value(fiter, 0)
|
||||||
|
except:
|
||||||
|
return
|
||||||
|
self.model.add_image(image, fid, only_thumbs)
|
||||||
|
|
||||||
|
self.model.unsaved_project = True
|
||||||
|
return
|
||||||
|
|
||||||
|
def on_remove_image_activate(self, menu_item):
|
||||||
|
if self.model.config.confd['delwarn']:
|
||||||
|
title = 'Delete images'
|
||||||
|
question = 'Delete all images?'
|
||||||
|
description = 'All images for selected items will be permanently'
|
||||||
|
description += ' removed from catalog.'
|
||||||
|
obj = Dialogs.Qst(title, question, description)
|
||||||
|
if not obj.run():
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
ids = self.__get_tv_selection_ids(self.view['result'])
|
||||||
|
for item_id in ids:
|
||||||
|
self.model.del_images(item_id)
|
||||||
|
except:
|
||||||
|
if __debug__:
|
||||||
|
print "c_search.py: on_remove_thumb_activate(): error on",
|
||||||
|
print "getting selected items or removing thumbnails"
|
||||||
|
return
|
||||||
|
|
||||||
|
self.model.unsaved_project = True
|
||||||
|
return
|
||||||
|
|
||||||
|
def on_edit_activate(self, menu_item):
|
||||||
|
try:
|
||||||
|
selection = self.view['result'].get_selection()
|
||||||
|
model, list_of_paths = selection.get_selected_rows()
|
||||||
|
fid = model.get_value(model.get_iter(list_of_paths[0]), 0)
|
||||||
|
except TypeError:
|
||||||
|
if __debug__:
|
||||||
|
print "c_main.py: on_edit2_activate(): 0",
|
||||||
|
print "zaznaczonych wierszy"
|
||||||
|
return
|
||||||
|
|
||||||
|
val = self.model.get_file_info(fid)
|
||||||
|
ret = Dialogs.EditDialog(val).run()
|
||||||
|
if ret:
|
||||||
|
self.model.rename(fid, ret['filename'])
|
||||||
|
self.model.update_desc_and_note(fid,
|
||||||
|
ret['description'], ret['note'])
|
||||||
|
self.model.unsaved_project = True
|
||||||
|
|
||||||
|
def on_delete_activate(self, menu_item):
|
||||||
|
dmodel = self.model.discs_tree
|
||||||
|
try:
|
||||||
|
selection = self.view['result'].get_selection()
|
||||||
|
model, list_of_paths = selection.get_selected_rows()
|
||||||
|
except TypeError:
|
||||||
|
return
|
||||||
|
|
||||||
|
if not list_of_paths:
|
||||||
|
Dialogs.Inf("Delete files", "No files selected",
|
||||||
|
"You have to select at least one file to delete.")
|
||||||
|
return
|
||||||
|
|
||||||
|
if self.model.config.confd['delwarn']:
|
||||||
|
description = "Selected files and directories will be "
|
||||||
|
description += "permanently\n removed from catalog."
|
||||||
|
obj = Dialogs.Qst("Delete files", "Delete files?", description)
|
||||||
|
if not obj.run():
|
||||||
|
return
|
||||||
|
|
||||||
|
def foreach_searchtree(zmodel, zpath, ziter, d):
|
||||||
|
if d[0] == zmodel.get_value(ziter, 0):
|
||||||
|
d[1].append(zpath)
|
||||||
|
return False
|
||||||
|
|
||||||
|
ids = []
|
||||||
|
for p in list_of_paths:
|
||||||
|
ids.append(model.get_value(model.get_iter(p), 0))
|
||||||
|
|
||||||
|
for fid in ids:
|
||||||
|
# delete from db
|
||||||
|
self.model.delete(fid)
|
||||||
|
|
||||||
|
self.model.unsaved_project = True
|
||||||
|
found = self.model.search(self.search_string)
|
||||||
|
self.__set_status_bar(found)
|
||||||
|
return
|
||||||
|
|
||||||
|
def on_rename_activate(self, menu_item):
|
||||||
|
try:
|
||||||
|
selection = self.view['result'].get_selection()
|
||||||
|
model, list_of_paths = selection.get_selected_rows()
|
||||||
|
fid = model.get_value(model.get_iter(list_of_paths[0]), 0)
|
||||||
|
except TypeError:
|
||||||
|
if __debug__:
|
||||||
|
print "c_main.py: on_edit2_activate(): 0",
|
||||||
|
print "zaznaczonych wierszy"
|
||||||
|
return
|
||||||
|
|
||||||
|
fid = model.get_value(model.get_iter(list_of_paths[0]), 0)
|
||||||
|
name = model.get_value(model.get_iter(list_of_paths[0]),2)
|
||||||
|
new_name = Dialogs.InputNewName(name).run()
|
||||||
|
|
||||||
|
if __debug__:
|
||||||
|
print "c_search.py: on_rename_activate(): label:", new_name
|
||||||
|
|
||||||
|
if new_name and new_name != name:
|
||||||
|
self.model.rename(fid, new_name)
|
||||||
|
self.model.unsaved_project = True
|
||||||
|
return
|
||||||
|
|
||||||
|
#####################
|
||||||
|
# observed properetis
|
||||||
|
|
||||||
|
#########################
|
||||||
|
# private class functions
|
||||||
|
def __set_status_bar(self, found):
|
||||||
|
"""sets number of founded items in statusbar"""
|
||||||
|
if found == 0:
|
||||||
|
msg = "No files found."
|
||||||
|
elif found == 1:
|
||||||
|
msg = "Found 1 file."
|
||||||
|
else:
|
||||||
|
msg = "Found %d files." % found
|
||||||
|
self.view['statusbar'].push(self.context_id, "%s" % msg)
|
||||||
|
|
||||||
|
def __get_tv_selection_ids(self, treeview):
|
||||||
|
"""get selection from treeview and return coresponding ids' from
|
||||||
|
connected model or None"""
|
||||||
|
ids = []
|
||||||
|
try:
|
||||||
|
selection = treeview.get_selection()
|
||||||
|
model, list_of_paths = selection.get_selected_rows()
|
||||||
|
for path in list_of_paths:
|
||||||
|
ids.append(model.get_value(model.get_iter(path), 0))
|
||||||
|
return ids
|
||||||
|
except:
|
||||||
|
# DEBUG: treeview have no selection or smth is broken
|
||||||
|
if __debug__:
|
||||||
|
print "c_search.py: __get_tv_selection_ids(): error on",
|
||||||
|
print "getting selected items"
|
||||||
|
return
|
||||||
|
return None
|
||||||
|
|
||||||
|
pass # end of class
|
||||||
49
src/gtkmvc/__init__.py
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
# Author: Roberto Cavada <cavada@fbk.eu>
|
||||||
|
#
|
||||||
|
# Copyright (c) 2005 by Roberto Cavada
|
||||||
|
#
|
||||||
|
# pygtkmvc is free software; you can redistribute it and/or
|
||||||
|
# modify it under the terms of the GNU Lesser General Public
|
||||||
|
# License as published by the Free Software Foundation; either
|
||||||
|
# version 2 of the License, or (at your option) any later version.
|
||||||
|
#
|
||||||
|
# pygtkmvc 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
|
||||||
|
# Lesser General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU Lesser General Public
|
||||||
|
# License along with this library; if not, write to the Free Software
|
||||||
|
# Foundation, Inc., 51 Franklin Street, Fifth Floor,
|
||||||
|
# Boston, MA 02110, USA.
|
||||||
|
#
|
||||||
|
# For more information on pygtkmvc see <http://pygtkmvc.sourceforge.net>
|
||||||
|
# or email to the author Roberto Cavada <cavada@fbk.eu>.
|
||||||
|
# Please report bugs to <cavada@fbk.eu>.
|
||||||
|
|
||||||
|
|
||||||
|
__all__ = ["model", "view", "controller", "observable", "observer", "support"]
|
||||||
|
|
||||||
|
__version = (1,2,2)
|
||||||
|
|
||||||
|
from model import Model, TreeStoreModel, ListStoreModel, TextBufferModel
|
||||||
|
from model_mt import ModelMT
|
||||||
|
from controller import Controller
|
||||||
|
from view import View
|
||||||
|
from observer import Observer
|
||||||
|
import observable
|
||||||
|
import adapters
|
||||||
|
|
||||||
|
def get_version(): return __version
|
||||||
|
|
||||||
|
def require(ver):
|
||||||
|
if isinstance(ver, str): ver = ver.split(".")
|
||||||
|
ver = tuple(map(int, ver))
|
||||||
|
|
||||||
|
if get_version() < ver:
|
||||||
|
raise AssertionError("gtkmvc required version '%s', found '%s'"\
|
||||||
|
% (ver, get_version()))
|
||||||
|
pass
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
25
src/gtkmvc/adapters/__init__.py
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
# Author: Roberto Cavada <cavada@fbk.eu>
|
||||||
|
#
|
||||||
|
# Copyright (c) 2007 by Roberto Cavada
|
||||||
|
#
|
||||||
|
# pygtkmvc is free software; you can redistribute it and/or
|
||||||
|
# modify it under the terms of the GNU Lesser General Public
|
||||||
|
# License as published by the Free Software Foundation; either
|
||||||
|
# version 2 of the License, or (at your option) any later version.
|
||||||
|
#
|
||||||
|
# pygtkmvc 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
|
||||||
|
# Lesser General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU Lesser General Public
|
||||||
|
# License along with this library; if not, write to the Free Software
|
||||||
|
# Foundation, Inc., 51 Franklin Street, Fifth Floor,
|
||||||
|
# Boston, MA 02110, USA.
|
||||||
|
#
|
||||||
|
# For more information on pygtkmvc see <http://pygtkmvc.sourceforge.net>
|
||||||
|
# or email to the author Roberto Cavada <cavada@fbk.eu>.
|
||||||
|
# Please report bugs to <cavada@fbk.eu>.
|
||||||
|
|
||||||
|
from gtkmvc.adapters.basic import Adapter, UserClassAdapter, RoUserClassAdapter
|
||||||
|
from gtkmvc.adapters.containers import StaticContainerAdapter
|
||||||
428
src/gtkmvc/adapters/basic.py
Normal file
@@ -0,0 +1,428 @@
|
|||||||
|
# Author: Roberto Cavada <cavada@fbk.eu>
|
||||||
|
#
|
||||||
|
# Copyright (c) 2007 by Roberto Cavada
|
||||||
|
#
|
||||||
|
# pygtkmvc is free software; you can redistribute it and/or
|
||||||
|
# modify it under the terms of the GNU Lesser General Public
|
||||||
|
# License as published by the Free Software Foundation; either
|
||||||
|
# version 2 of the License, or (at your option) any later version.
|
||||||
|
#
|
||||||
|
# pygtkmvc 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
|
||||||
|
# Lesser General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU Lesser General Public
|
||||||
|
# License along with this library; if not, write to the Free Software
|
||||||
|
# Foundation, Inc., 51 Franklin Street, Fifth Floor,
|
||||||
|
# Boston, MA 02110, USA.
|
||||||
|
#
|
||||||
|
# For more information on pygtkmvc see <http://pygtkmvc.sourceforge.net>
|
||||||
|
# or email to the author Roberto Cavada <cavada@fbk.eu>.
|
||||||
|
# Please report bugs to <cavada@fbk.eu>.
|
||||||
|
|
||||||
|
|
||||||
|
import types
|
||||||
|
import gtk
|
||||||
|
import time
|
||||||
|
|
||||||
|
from gtkmvc.adapters.default import *
|
||||||
|
from gtkmvc.observer import Observer
|
||||||
|
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------
|
||||||
|
class Adapter (Observer):
|
||||||
|
|
||||||
|
def __init__(self, model, prop_name,
|
||||||
|
prop_read=None, prop_write=None,
|
||||||
|
value_error=None):
|
||||||
|
"""
|
||||||
|
Creates a new adapter that handles setting of value of a
|
||||||
|
model single model property when a corresponding widgets set
|
||||||
|
is changed and viceversa when the property is also
|
||||||
|
observable.
|
||||||
|
|
||||||
|
This class handles only assignments to properties. For other
|
||||||
|
kinds of setting (e.g. user-defined classes used as
|
||||||
|
observable properties, containers, etc.) use other types of
|
||||||
|
Adapters derived from this class.
|
||||||
|
|
||||||
|
prop_name is the model's property name (as a string). It is
|
||||||
|
possible to use a dotted notation to identify a property
|
||||||
|
contained into a hierarchy of models. For example 'a.b.c'
|
||||||
|
identifies property 'c' into model 'b' inside model 'a',
|
||||||
|
where model 'a' is an attribute of given top level model.
|
||||||
|
Last name must be an observable or non-observable attribute,
|
||||||
|
and previous names (if specified) must all refer to
|
||||||
|
instances of class Model. First name from the left must be
|
||||||
|
the name of a model instance inside the given model.
|
||||||
|
|
||||||
|
prop_{write,read} are two optional functions that apply
|
||||||
|
custom modifications to the value of the property before
|
||||||
|
setting and reading it. Both take a value and must return a
|
||||||
|
transformed value whose type must be compatible with the
|
||||||
|
type of the property.
|
||||||
|
|
||||||
|
value_error can be a function (or a method) to be called
|
||||||
|
when a ValueError exception occurs while trying to set a
|
||||||
|
wrong value for the property inside the model. The function
|
||||||
|
will receive: the adapter, the property name and the value
|
||||||
|
coming from the widget that offended the model.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# registration is delayed, as we need to create possible
|
||||||
|
# listener before:
|
||||||
|
Observer.__init__(self)
|
||||||
|
|
||||||
|
self._prop_name = prop_name
|
||||||
|
self._prop_read = prop_read
|
||||||
|
self._prop_write = prop_write
|
||||||
|
self._value_error = value_error
|
||||||
|
self._wid = None
|
||||||
|
self._wid_info = {}
|
||||||
|
|
||||||
|
# this flag is set when self is changing the property or the
|
||||||
|
# widget, in order to avoid infinite looping.
|
||||||
|
self._itsme = False
|
||||||
|
|
||||||
|
self._connect_model(model)
|
||||||
|
return
|
||||||
|
|
||||||
|
def connect_widget(self, wid,
|
||||||
|
getter=None, setter=None,
|
||||||
|
signal=None, arg=None, update=True):
|
||||||
|
|
||||||
|
"""
|
||||||
|
Called when the widget is instantiated, and the adapter is
|
||||||
|
ready to connect the widget and the property inside the
|
||||||
|
observed model. arg is the (optional) argument that will be
|
||||||
|
passed when connecting the signal.
|
||||||
|
|
||||||
|
getter and setter are the (optional) methods used
|
||||||
|
for reading and writing the widget's value. When not
|
||||||
|
specified, default getter and setter will be guessed by
|
||||||
|
looking at the widget type the adapter will be connected
|
||||||
|
with. Guessing is carried out by querying information
|
||||||
|
specified into module 'adapters.default'.
|
||||||
|
|
||||||
|
Finally, if update is false, the widget will not be updated
|
||||||
|
"""
|
||||||
|
|
||||||
|
if self._wid_info.has_key(wid):
|
||||||
|
raise ValueError("Widget " + str(wid) + " was already connected")
|
||||||
|
|
||||||
|
wid_type = None
|
||||||
|
|
||||||
|
if None in (getter, setter, signal):
|
||||||
|
w = search_adapter_info(wid)
|
||||||
|
if getter is None: getter = w[GETTER]
|
||||||
|
if setter is None:
|
||||||
|
setter = w[SETTER]
|
||||||
|
wid_type = w[WIDTYPE]
|
||||||
|
pass
|
||||||
|
|
||||||
|
if signal is None: signal = w[SIGNAL]
|
||||||
|
pass
|
||||||
|
|
||||||
|
# saves information about the widget
|
||||||
|
self._wid_info[wid] = (getter, setter, wid_type)
|
||||||
|
|
||||||
|
# connects the widget
|
||||||
|
if signal:
|
||||||
|
if arg: wid.connect(signal, self._on_wid_changed, arg)
|
||||||
|
else: wid.connect(signal, self._on_wid_changed)
|
||||||
|
pass
|
||||||
|
|
||||||
|
self._wid = wid
|
||||||
|
|
||||||
|
# updates the widget:
|
||||||
|
if update: self.update_widget()
|
||||||
|
return
|
||||||
|
|
||||||
|
def update_model(self):
|
||||||
|
"""Forces the property to be updated from the value hold by
|
||||||
|
the widget. This method should be called directly by the
|
||||||
|
user in very unusual conditions."""
|
||||||
|
self._write_property(self._read_widget())
|
||||||
|
return
|
||||||
|
|
||||||
|
def update_widget(self):
|
||||||
|
"""Forces the widget to be updated from the property
|
||||||
|
value. This method should be called directly by the user
|
||||||
|
when the property is not observable, or in very unusual
|
||||||
|
conditions."""
|
||||||
|
self._write_widget(self._read_property())
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------
|
||||||
|
# Private methods
|
||||||
|
# ----------------------------------------------------------------------
|
||||||
|
def _connect_model(self, model):
|
||||||
|
"""
|
||||||
|
Used internally to connect the property into the model, and
|
||||||
|
register self as a value observer for that property"""
|
||||||
|
|
||||||
|
parts = self._prop_name.split(".")
|
||||||
|
if len(parts) > 1:
|
||||||
|
# identifies the model
|
||||||
|
models = parts[:-1]
|
||||||
|
for name in models:
|
||||||
|
model = getattr(model, name)
|
||||||
|
if not isinstance(model, gtkmvc.Model):
|
||||||
|
raise TypeError("Attribute '" + name +
|
||||||
|
"' was expected to be a Model, but found: " +
|
||||||
|
str(model))
|
||||||
|
pass
|
||||||
|
prop = parts[-1]
|
||||||
|
else: prop = parts[0]
|
||||||
|
|
||||||
|
# prop is inside model?
|
||||||
|
if not hasattr(model, prop):
|
||||||
|
raise ValueError("Attribute '" + prop +
|
||||||
|
"' not found in model " + str(model))
|
||||||
|
|
||||||
|
# is it observable?
|
||||||
|
if model.has_property(prop):
|
||||||
|
# we need to create an observing method before registering
|
||||||
|
self._add_method(self._get_observer_src(prop))
|
||||||
|
pass
|
||||||
|
|
||||||
|
self._prop = getattr(model, prop)
|
||||||
|
self._prop_name = prop
|
||||||
|
|
||||||
|
# registration of model:
|
||||||
|
self.register_model(model)
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
def _get_observer_src(self, prop_name):
|
||||||
|
"""This is the code for an value change observer"""
|
||||||
|
return """def property_%s_value_change(self, model, old, new):
|
||||||
|
if self._itsme or old == new: return
|
||||||
|
self._on_prop_changed()""" % prop_name
|
||||||
|
|
||||||
|
|
||||||
|
def _add_method(self, src):
|
||||||
|
"""Private service to add a new method to the instance,
|
||||||
|
given method code"""
|
||||||
|
|
||||||
|
from gtkmvc.support.utils import get_function_from_source
|
||||||
|
import new
|
||||||
|
|
||||||
|
func = get_function_from_source(src)
|
||||||
|
meth = new.instancemethod(func, self, self.__class__)
|
||||||
|
setattr(self, func.__name__, meth)
|
||||||
|
return
|
||||||
|
|
||||||
|
def _get_property(self):
|
||||||
|
"""Private method that returns the value currently stored
|
||||||
|
into the property"""
|
||||||
|
return getattr(self.get_model(), self._prop_name)
|
||||||
|
#return self._prop # bug fix reported by A. Dentella
|
||||||
|
|
||||||
|
def _set_property(self, val):
|
||||||
|
"""Private method that sets the value currently of the property."""
|
||||||
|
return setattr(self.get_model(), self._prop_name, val)
|
||||||
|
|
||||||
|
def _read_property(self, *args):
|
||||||
|
"""Returns the (possibly transformed) value that is stored
|
||||||
|
into the property"""
|
||||||
|
if self._prop_read: return self._prop_read(self._get_property(*args))
|
||||||
|
return self._get_property(*args)
|
||||||
|
|
||||||
|
def _write_property(self, val, *args):
|
||||||
|
"""Sets the value of property. Given val is transformed
|
||||||
|
accodingly to prop_write function when specified at
|
||||||
|
construction-time. A try to cast the value to the property
|
||||||
|
type is given."""
|
||||||
|
|
||||||
|
# 'finally' would be better here, but not supported in 2.4 :(
|
||||||
|
try:
|
||||||
|
totype = type(self._get_property(*args))
|
||||||
|
val_prop = self._cast_value(val, totype)
|
||||||
|
if self._prop_write: val_prop = self._prop_write(val_prop)
|
||||||
|
|
||||||
|
self._itsme = True
|
||||||
|
self._set_property(val_prop, *args)
|
||||||
|
|
||||||
|
except ValueError:
|
||||||
|
self._itsme = False
|
||||||
|
if self._value_error: self._value_error(self, self._prop_name, val)
|
||||||
|
else: raise
|
||||||
|
pass
|
||||||
|
|
||||||
|
except: self._itsme = False; raise
|
||||||
|
|
||||||
|
self._itsme = False
|
||||||
|
return
|
||||||
|
|
||||||
|
def _read_widget(self):
|
||||||
|
"""Returns the value currently stored into the widget, after
|
||||||
|
transforming it accordingly to possibly specified function."""
|
||||||
|
getter = self._wid_info[self._wid][0]
|
||||||
|
return getter(self._wid)
|
||||||
|
|
||||||
|
def _write_widget(self, val):
|
||||||
|
"""Writes value into the widget. If specified, user setter
|
||||||
|
is invoked."""
|
||||||
|
self._itsme = True
|
||||||
|
try:
|
||||||
|
setter = self._wid_info[self._wid][1]
|
||||||
|
wtype = self._wid_info[self._wid][2]
|
||||||
|
if wtype is not None: setter(self._wid, self._cast_value(val, wtype))
|
||||||
|
else: setter(self._wid, val)
|
||||||
|
finally:
|
||||||
|
self._itsme = False
|
||||||
|
pass
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
|
def _cast_value(self, val, totype):
|
||||||
|
"""Casts given val to given totype. Raises TypeError if not able to cast."""
|
||||||
|
t = type(val)
|
||||||
|
if issubclass(t, totype): return val
|
||||||
|
if issubclass(totype, types.StringType): return str(val)
|
||||||
|
if issubclass(t, types.StringType):
|
||||||
|
if issubclass(totype, types.IntType):
|
||||||
|
if val: return int(float(val))
|
||||||
|
return 0
|
||||||
|
if issubclass(totype, types.FloatType):
|
||||||
|
if val: return float(val)
|
||||||
|
return 0.0
|
||||||
|
pass
|
||||||
|
|
||||||
|
raise TypeError("Not able to cast " + str(t) + " to " + str(totype))
|
||||||
|
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------
|
||||||
|
# Callbacks and observation
|
||||||
|
# ----------------------------------------------------------------------
|
||||||
|
|
||||||
|
def _on_wid_changed(self, wid):
|
||||||
|
"""Called when the widget is changed"""
|
||||||
|
if self._itsme: return
|
||||||
|
self.update_model()
|
||||||
|
return
|
||||||
|
|
||||||
|
def _on_prop_changed(self):
|
||||||
|
"""Called by the observation code, when the value in the
|
||||||
|
observed property is changed"""
|
||||||
|
if not self._itsme: self.update_widget()
|
||||||
|
return
|
||||||
|
|
||||||
|
pass # end of class Adapter
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#----------------------------------------------------------------------
|
||||||
|
class UserClassAdapter (Adapter):
|
||||||
|
"""
|
||||||
|
This class handles the communication between a widget and a
|
||||||
|
class instance (possibly observable) that is a property inside
|
||||||
|
the model. The value to be shown is taken and stored by using a
|
||||||
|
getter and a setter. getter and setter can be: names of user
|
||||||
|
class methods, bound or unbound methods of the user class, or a
|
||||||
|
function that will receive the user class instance and possible
|
||||||
|
arguments whose number depends on whether it is a getter or a
|
||||||
|
setter."""
|
||||||
|
|
||||||
|
def __init__(self, model, prop_name,
|
||||||
|
getter, setter,
|
||||||
|
prop_read=None, prop_write=None,
|
||||||
|
value_error=None):
|
||||||
|
|
||||||
|
Adapter.__init__(self, model, prop_name,
|
||||||
|
prop_read, prop_write, value_error)
|
||||||
|
|
||||||
|
self._getter = self._resolve_to_func(getter)
|
||||||
|
self._setter = self._resolve_to_func(setter)
|
||||||
|
return
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------
|
||||||
|
# Private methods
|
||||||
|
# ----------------------------------------------------------------------
|
||||||
|
|
||||||
|
def _resolve_to_func(self, what):
|
||||||
|
"""This method resolves whatever is passed: a string, a
|
||||||
|
bound or unbound method, a function, to make it a
|
||||||
|
function. This makes internal handling of setter and getter
|
||||||
|
uniform and easier."""
|
||||||
|
if isinstance(what, types.StringType):
|
||||||
|
what = getattr(Adapter._get_property(self), what)
|
||||||
|
pass
|
||||||
|
|
||||||
|
# makes it an unbounded function if needed
|
||||||
|
if type(what) == types.MethodType: what = what.im_func
|
||||||
|
|
||||||
|
if not type(what) == types.FunctionType: raise TypeError("Expected a method name, a method or a function")
|
||||||
|
return what
|
||||||
|
|
||||||
|
|
||||||
|
def _get_observer_src(self, prop_name):
|
||||||
|
"""This is the code for a method after_change observer"""
|
||||||
|
return """def property_%s_after_change(self, model, \
|
||||||
|
instance, meth_name, res, args, kwargs):
|
||||||
|
if self._itsme: return
|
||||||
|
self._on_prop_changed(instance, meth_name, res, args, kwargs)""" % prop_name
|
||||||
|
|
||||||
|
|
||||||
|
def _on_prop_changed(self, instance, meth_name, res, args, kwargs):
|
||||||
|
"""Called by the observation code, when a modifying method
|
||||||
|
is called"""
|
||||||
|
Adapter._on_prop_changed(self)
|
||||||
|
return
|
||||||
|
|
||||||
|
def _get_property(self, *args):
|
||||||
|
"""Private method that returns the value currently stored
|
||||||
|
into the property"""
|
||||||
|
val = self._getter(Adapter._get_property(self), *args)
|
||||||
|
if self._prop_read: return self._prop_read(val, *args)
|
||||||
|
return val
|
||||||
|
|
||||||
|
def _set_property(self, val, *args):
|
||||||
|
"""Private method that sets the value currently of the property"""
|
||||||
|
if self._prop_write: val = self._prop_write(val)
|
||||||
|
return self._setter(Adapter._get_property(self), val, *args)
|
||||||
|
|
||||||
|
pass # end of class UserClassAdapter
|
||||||
|
# ----------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#----------------------------------------------------------------------
|
||||||
|
class RoUserClassAdapter (UserClassAdapter):
|
||||||
|
"""
|
||||||
|
This class is for Read-Only user classes. RO classes are those
|
||||||
|
whose setting methods do not change the instance, but return a
|
||||||
|
new instance that has been changed accordingly to the setters
|
||||||
|
semantics. An example is python datetime class, whose replace
|
||||||
|
method does not change the instance it is invoked on, but
|
||||||
|
returns a new datetime instance.
|
||||||
|
|
||||||
|
This class is likely to be used very rarely.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, model, prop_name,
|
||||||
|
getter, setter,
|
||||||
|
prop_read=None, prop_write=None,
|
||||||
|
value_error=None):
|
||||||
|
|
||||||
|
UserClassAdapter.__init__(self, model, prop_name,
|
||||||
|
getter, setter,
|
||||||
|
prop_read, prop_write, value_error)
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------
|
||||||
|
# Private methods
|
||||||
|
# ----------------------------------------------------------------------
|
||||||
|
|
||||||
|
def _set_property(self, val, *args):
|
||||||
|
"""Private method that sets the value currently of the property"""
|
||||||
|
val = UserClassAdapter._set_property(self, val, *args)
|
||||||
|
if val: Adapter._set_property(self, val, *args)
|
||||||
|
return val
|
||||||
|
|
||||||
|
pass # end of class RoUserClassAdapter
|
||||||
|
# ----------------------------------------------------------------------
|
||||||
217
src/gtkmvc/adapters/containers.py
Normal file
@@ -0,0 +1,217 @@
|
|||||||
|
# Author: Roberto Cavada <cavada@fbk.eu>
|
||||||
|
#
|
||||||
|
# Copyright (c) 2007 by Roberto Cavada
|
||||||
|
#
|
||||||
|
# pygtkmvc is free software; you can redistribute it and/or
|
||||||
|
# modify it under the terms of the GNU Lesser General Public
|
||||||
|
# License as published by the Free Software Foundation; either
|
||||||
|
# version 2 of the License, or (at your option) any later version.
|
||||||
|
#
|
||||||
|
# pygtkmvc 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
|
||||||
|
# Lesser General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU Lesser General Public
|
||||||
|
# License along with this library; if not, write to the Free Software
|
||||||
|
# Foundation, Inc., 51 Franklin Street, Fifth Floor,
|
||||||
|
# Boston, MA 02110, USA.
|
||||||
|
#
|
||||||
|
# For more information on pygtkmvc see <http://pygtkmvc.sourceforge.net>
|
||||||
|
# or email to the author Roberto Cavada <cavada@fbk.eu>.
|
||||||
|
# Please report bugs to <cavada@fbk.eu>.
|
||||||
|
|
||||||
|
|
||||||
|
import types
|
||||||
|
import gtk
|
||||||
|
|
||||||
|
from gtkmvc.adapters.basic import UserClassAdapter, Adapter
|
||||||
|
|
||||||
|
from gtkmvc.adapters.default import *
|
||||||
|
from gtkmvc.observer import Observer
|
||||||
|
from gtkmvc.support.wrappers import ObsMapWrapper
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------
|
||||||
|
class StaticContainerAdapter (UserClassAdapter):
|
||||||
|
"""
|
||||||
|
This class can be used to bound a set of widgets to a property
|
||||||
|
that is a container, like a tuple, a list or a map, or in
|
||||||
|
general a class that implements __getitem__ and __setitem__
|
||||||
|
methods.
|
||||||
|
|
||||||
|
From the other hand, the set of widgets can be a list provided
|
||||||
|
by the user, or a container widget like a Box, a notebook, etc.
|
||||||
|
Widgets will be linked by their position when the property is
|
||||||
|
list-like, or by their name when the property is map-like.
|
||||||
|
|
||||||
|
This class supports only properties that are static containers,
|
||||||
|
i.e. those containers that do not change their length
|
||||||
|
dynamically. If the container grows up in length, no change will
|
||||||
|
occur in the view-side.
|
||||||
|
"""
|
||||||
|
def __init__(self, model, prop_name,
|
||||||
|
prop_read=None, prop_write=None, value_error=None):
|
||||||
|
|
||||||
|
UserClassAdapter.__init__(self, model, prop_name,
|
||||||
|
lambda c,i: c.__getitem__(i),
|
||||||
|
lambda c,v,i: c.__setitem__(i,v),
|
||||||
|
prop_read, prop_write,
|
||||||
|
value_error)
|
||||||
|
|
||||||
|
prop = Adapter._get_property(self)
|
||||||
|
#prop = self._get_property() # bug fix reported by A. Dentella
|
||||||
|
if not (hasattr(prop, "__getitem__") and
|
||||||
|
hasattr(prop, "__setitem__")):
|
||||||
|
raise TypeError("Property " + self._prop_name +
|
||||||
|
" is not a valid container")
|
||||||
|
|
||||||
|
|
||||||
|
self._prop_is_map = isinstance(prop, types.DictType) or \
|
||||||
|
isinstance(prop, ObsMapWrapper)
|
||||||
|
# contained widgets
|
||||||
|
self._idx2wid = {}
|
||||||
|
self._wid2idx = {}
|
||||||
|
|
||||||
|
self._widgets = None
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
def connect_widget(self, wid, getters=None, setters=None,
|
||||||
|
signals=None, arg=None):
|
||||||
|
"""
|
||||||
|
Called when the widget is instantiated, and the adapter is
|
||||||
|
ready to connect the widgets inside it (if a container) or
|
||||||
|
each widget if wid is a list of widgets. getters and setters
|
||||||
|
can be None, a function or a list or a map of
|
||||||
|
functions. signals can be None, a signal name, or a list or
|
||||||
|
a map of signal names. When maps are used, keys can be
|
||||||
|
widgets or widget names. The length of the possible lists or
|
||||||
|
maps must be lesser or equal to the number of widgets that
|
||||||
|
will be connected.
|
||||||
|
"""
|
||||||
|
|
||||||
|
if isinstance(wid, gtk.Container): self._widgets = wid.get_children()
|
||||||
|
elif isinstance(wid, types.ListType) or isinstance(wid, types.TupleType): self._widgets = wid
|
||||||
|
else: raise TypeError("widget must be either a gtk.Container or a list or tuple")
|
||||||
|
|
||||||
|
# prepares the mappings:
|
||||||
|
for idx, w in enumerate(self._widgets):
|
||||||
|
if self._prop_is_map: idx=w.get_name()
|
||||||
|
self._idx2wid[idx] = w
|
||||||
|
self._wid2idx[w] = idx
|
||||||
|
pass
|
||||||
|
|
||||||
|
# prepares the lists for signals
|
||||||
|
getters = self.__handle_par("getters", getters)
|
||||||
|
setters = self.__handle_par("setters", setters)
|
||||||
|
signals = self.__handle_par("signals", signals)
|
||||||
|
|
||||||
|
for wi,ge,se,si in zip(self._widgets, getters, setters, signals):
|
||||||
|
if type(ge) == types.MethodType: ge = ge.im_func
|
||||||
|
if type(se) == types.MethodType: se = se.im_func
|
||||||
|
UserClassAdapter.connect_widget(self, wi, ge, se, si, arg, False)
|
||||||
|
pass
|
||||||
|
|
||||||
|
self.update_widget()
|
||||||
|
self._wid = wid
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
def update_model(self, idx=None):
|
||||||
|
"""Updates the value of property at given index. If idx is
|
||||||
|
None, all controlled indices will be updated. This method
|
||||||
|
should be called directly by the user in very unusual
|
||||||
|
conditions."""
|
||||||
|
if idx is None:
|
||||||
|
for w in self._widgets:
|
||||||
|
idx = self._get_idx_from_widget(w)
|
||||||
|
self._write_property(self._read_widget(idx), idx)
|
||||||
|
pass
|
||||||
|
pass
|
||||||
|
else: self._write_property(self._read_widget(idx), idx)
|
||||||
|
return
|
||||||
|
|
||||||
|
def update_widget(self, idx=None):
|
||||||
|
"""Forces the widget at given index to be updated from the
|
||||||
|
property value. If index is not given, all controlled
|
||||||
|
widgets will be updated. This method should be called
|
||||||
|
directly by the user when the property is not observable, or
|
||||||
|
in very unusual conditions."""
|
||||||
|
if idx is None:
|
||||||
|
for w in self._widgets:
|
||||||
|
idx = self._get_idx_from_widget(w)
|
||||||
|
self._write_widget(self._read_property(idx), idx)
|
||||||
|
pass
|
||||||
|
else: self._write_widget(self._read_property(idx), idx)
|
||||||
|
return
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------
|
||||||
|
# Private methods
|
||||||
|
# ----------------------------------------------------------------------
|
||||||
|
|
||||||
|
def _get_idx_from_widget(self, wid):
|
||||||
|
"""Given a widget, returns the corresponding index for the
|
||||||
|
model. Returned value can be either an integer or a string"""
|
||||||
|
return self._wid2idx[wid]
|
||||||
|
|
||||||
|
def _get_widget_from_idx(self, idx):
|
||||||
|
"""Given an index, returns the corresponding widget for the view.
|
||||||
|
Given index can be either an integer or a string"""
|
||||||
|
return self._idx2wid[idx]
|
||||||
|
|
||||||
|
|
||||||
|
def _read_widget(self, idx):
|
||||||
|
sav = self._wid
|
||||||
|
self._wid = self._get_widget_from_idx(idx)
|
||||||
|
val = UserClassAdapter._read_widget(self)
|
||||||
|
self._wid = sav
|
||||||
|
return val
|
||||||
|
|
||||||
|
def _write_widget(self, val, idx):
|
||||||
|
sav = self._wid
|
||||||
|
self._wid = self._get_widget_from_idx(idx)
|
||||||
|
UserClassAdapter._write_widget(self, val)
|
||||||
|
self._wid = sav
|
||||||
|
return
|
||||||
|
|
||||||
|
# This is a private service to factorize code of connect_widget
|
||||||
|
def __handle_par(self, name, par):
|
||||||
|
if par is None or type(par) in (types.FunctionType,
|
||||||
|
types.MethodType, types.StringType):
|
||||||
|
par = [par] * len(self._widgets)
|
||||||
|
pass
|
||||||
|
|
||||||
|
elif isinstance(par, types.DictType):
|
||||||
|
val = []
|
||||||
|
for w in self._widgets:
|
||||||
|
if par.has_key(w): val.append(par[w])
|
||||||
|
elif par.has_key(w.get_name()): val.append(par[w.get_name()])
|
||||||
|
else: val.append(None)
|
||||||
|
pass
|
||||||
|
par = val
|
||||||
|
pass
|
||||||
|
|
||||||
|
elif isinstance(par, types.ListType) or isinstance(par, types.TupleType):
|
||||||
|
par = list(par)
|
||||||
|
par.extend([None]*(len(self._widgets)-len(par)))
|
||||||
|
pass
|
||||||
|
|
||||||
|
else: raise TypeError("Parameter %s has an invalid type (should be None, a sequence or a string)" % name)
|
||||||
|
|
||||||
|
return par
|
||||||
|
|
||||||
|
|
||||||
|
# Callbacks:
|
||||||
|
def _on_wid_changed(self, wid):
|
||||||
|
"""Called when the widget is changed"""
|
||||||
|
if self._itsme: return
|
||||||
|
self.update_model(self._get_idx_from_widget(wid))
|
||||||
|
return
|
||||||
|
|
||||||
|
def _on_prop_changed(self, instance, meth_name, res, args, kwargs):
|
||||||
|
"""Called by the observation code, we are interested in
|
||||||
|
__setitem__"""
|
||||||
|
if not self._itsme and meth_name == "__setitem__": self.update_widget(args[0])
|
||||||
|
return
|
||||||
|
|
||||||
|
pass # end of class StaticContainerAdapter
|
||||||
55
src/gtkmvc/adapters/default.py
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
__all__ = ("search_adapter_info",
|
||||||
|
"SIGNAL", "GETTER", "SETTER", "WIDTYPE")
|
||||||
|
|
||||||
|
import types
|
||||||
|
import gtk
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------
|
||||||
|
# This list defines a default behavior for widgets.
|
||||||
|
# If no particular behaviour has been specified, adapters will
|
||||||
|
# use information contained into this list to create themself.
|
||||||
|
# This list is ordered: the earlier a widget occurs, the better it
|
||||||
|
# will be matched by the search function.
|
||||||
|
# ----------------------------------------------------------------------
|
||||||
|
__def_adapter = [ # class, default signal, getter, setter, value type
|
||||||
|
(gtk.Entry, "changed", gtk.Entry.get_text, gtk.Entry.set_text, types.StringType),
|
||||||
|
(gtk.Label, None, gtk.Label.get_text, gtk.Label.set_text, types.StringType),
|
||||||
|
(gtk.Arrow, None, lambda a: a.get_property("arrow-type"),
|
||||||
|
lambda a,v: a.set(v,a.get_property("shadow-type")), gtk.ArrowType),
|
||||||
|
(gtk.ToggleButton, "toggled", gtk.ToggleButton.get_active, gtk.ToggleButton.set_active, types.BooleanType),
|
||||||
|
(gtk.CheckMenuItem, "toggled", gtk.CheckMenuItem.get_active, gtk.CheckMenuItem.set_active, types.BooleanType),
|
||||||
|
(gtk.Expander, "activate", lambda w: not w.get_expanded(), gtk.Expander.set_expanded, types.BooleanType),
|
||||||
|
(gtk.ColorButton, "color-set", gtk.ColorButton.get_color, gtk.ColorButton.set_color, gtk.gdk.Color),
|
||||||
|
(gtk.ColorSelection, "color-changed", gtk.ColorSelection.get_current_color, gtk.ColorSelection.set_current_color, gtk.gdk.Color),
|
||||||
|
]
|
||||||
|
|
||||||
|
if gtk.pygtk_version >= (2,10,0):
|
||||||
|
# conditionally added support
|
||||||
|
__def_adapter.append(
|
||||||
|
(gtk.LinkButton, "clicked", gtk.LinkButton.get_uri, gtk.LinkButton.set_uri, types.StringType))
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
# constants to access values:
|
||||||
|
SIGNAL =1
|
||||||
|
GETTER =2
|
||||||
|
SETTER =3
|
||||||
|
WIDTYPE =4
|
||||||
|
# ----------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
# To optimize the search
|
||||||
|
__memoize__ = {}
|
||||||
|
def search_adapter_info(wid):
|
||||||
|
"""Given a widget returns the default tuple found in __def_adapter"""
|
||||||
|
t = type(wid)
|
||||||
|
if __memoize__.has_key(t): return __memoize__[t]
|
||||||
|
|
||||||
|
for w in __def_adapter:
|
||||||
|
if isinstance(wid, w[0]):
|
||||||
|
__memoize__[t] = w
|
||||||
|
return w
|
||||||
|
pass
|
||||||
|
|
||||||
|
raise TypeError("Adapter type " + str(t) + " not found among supported adapters")
|
||||||
|
|
||||||
218
src/gtkmvc/controller.py
Normal file
@@ -0,0 +1,218 @@
|
|||||||
|
# Author: Roberto Cavada <cavada@fbk.eu>
|
||||||
|
#
|
||||||
|
# Copyright (c) 2005 by Roberto Cavada
|
||||||
|
#
|
||||||
|
# pygtkmvc is free software; you can redistribute it and/or
|
||||||
|
# modify it under the terms of the GNU Lesser General Public
|
||||||
|
# License as published by the Free Software Foundation; either
|
||||||
|
# version 2 of the License, or (at your option) any later version.
|
||||||
|
#
|
||||||
|
# pygtkmvc 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
|
||||||
|
# Lesser General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU Lesser General Public
|
||||||
|
# License along with this library; if not, write to the Free Software
|
||||||
|
# Foundation, Inc., 51 Franklin Street, Fifth Floor,
|
||||||
|
# Boston, MA 02110, USA.
|
||||||
|
#
|
||||||
|
# For more information on pygtkmvc see <http://pygtkmvc.sourceforge.net>
|
||||||
|
# or email to the author Roberto Cavada <cavada@fbk.eu>.
|
||||||
|
# Please report bugs to <cavada@fbk.eu>.
|
||||||
|
|
||||||
|
|
||||||
|
from gtkmvc.observer import Observer
|
||||||
|
import types
|
||||||
|
|
||||||
|
|
||||||
|
class Controller (Observer):
|
||||||
|
"""
|
||||||
|
We put all of our gtk signal handlers into a class. This lets
|
||||||
|
us bind all of them at once, because their names are in the
|
||||||
|
class dict. This class automatically register its instances as
|
||||||
|
observers into the corresponding model. Also, when a view is
|
||||||
|
created, the view calls method register_view, which can be
|
||||||
|
oveloaded in order to connect signals and perform other specific
|
||||||
|
operation. A controller possibly handles and contains also a set
|
||||||
|
of adapters that makes easier to connect widgets and observable
|
||||||
|
properties into the model.
|
||||||
|
|
||||||
|
parameter spurious controls the way spurious value change
|
||||||
|
notifications are handled. If True, assignments to observable
|
||||||
|
properties that do not actually change the value are
|
||||||
|
notified anyway."""
|
||||||
|
|
||||||
|
def __init__(self, model, spurious=False):
|
||||||
|
Observer.__init__(self, model, spurious)
|
||||||
|
|
||||||
|
self.view = None
|
||||||
|
self.__adapters = []
|
||||||
|
return
|
||||||
|
|
||||||
|
def register_view(self, view):
|
||||||
|
"""
|
||||||
|
This method is called by the framework when registering a
|
||||||
|
view. Derived classes can exploit this call to connect
|
||||||
|
signals manually, create and connect treeview, textview,
|
||||||
|
etc.
|
||||||
|
"""
|
||||||
|
assert(self.view is None)
|
||||||
|
self.view = view
|
||||||
|
self.register_adapters()
|
||||||
|
return
|
||||||
|
|
||||||
|
def register_adapters(self):
|
||||||
|
"""
|
||||||
|
This method is called by register_view during view
|
||||||
|
registration process, when it is time to possibly create all
|
||||||
|
adapters. model and view can safely be taken from self.model
|
||||||
|
and self.view. Derived classes can specilize this method. In
|
||||||
|
this implementation the method does nothing.
|
||||||
|
"""
|
||||||
|
assert(self.model is not None)
|
||||||
|
assert(self.view is not None)
|
||||||
|
return
|
||||||
|
|
||||||
|
def adapt(self, *args):
|
||||||
|
"""
|
||||||
|
Adapts a set of (observable) properties and a set of
|
||||||
|
widgets, by connecting them.
|
||||||
|
|
||||||
|
This method can be used to simplify the creation of one or
|
||||||
|
more adapters, by letting the framework do most of the work
|
||||||
|
needed to connect ('adapt') properties from one hand, and
|
||||||
|
widgets on the other.
|
||||||
|
|
||||||
|
This method is provided in three flavours:
|
||||||
|
|
||||||
|
1. An instance of an Adapter class can be created by the
|
||||||
|
caller and passed as a unique argument. The adapter must
|
||||||
|
be already fully configured.
|
||||||
|
|
||||||
|
2. The name of a model's property is passed as a unique
|
||||||
|
argument. A correponding widget name is searched and if
|
||||||
|
found, an adapter is created. Name matching is performed
|
||||||
|
by searching into view's widget names for words that end
|
||||||
|
with the given property name. Matching is case
|
||||||
|
insensitive and words can be separated by spaces,
|
||||||
|
underscore (_) and CapitalizedWords. For example property
|
||||||
|
'prop' will match widget 'cb_prop'. If no matches or
|
||||||
|
multiple matches are found, a ValueError will be raised.
|
||||||
|
The way names are matched can be customized by deriving
|
||||||
|
method match_prop_name.
|
||||||
|
|
||||||
|
3. Two strings can be passed, respectively containing the
|
||||||
|
name of an observable property in the model, and the name
|
||||||
|
of a widget in the view.
|
||||||
|
|
||||||
|
Flavour 1 allows for a full control, but flavour 2 and 3
|
||||||
|
make easier to create adpaters with basic (default)
|
||||||
|
behaviour.
|
||||||
|
|
||||||
|
This method can be called into the method register_adapters
|
||||||
|
which derived classes can specialise.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# checks arguments
|
||||||
|
n = len(args)
|
||||||
|
if n not in (1,2): raise TypeError("adapt() takes 1 or 2 arguments (%d given)" % n)
|
||||||
|
|
||||||
|
if n == 1: #one argument
|
||||||
|
from gtkmvc.adapters.basic import Adapter
|
||||||
|
|
||||||
|
if isinstance(args[0], Adapter): adapters = (args[0],)
|
||||||
|
|
||||||
|
elif isinstance(args[0], types.StringType):
|
||||||
|
prop_name = args[0]
|
||||||
|
names = []
|
||||||
|
for k in self.view:
|
||||||
|
if self.__match_prop_name(prop_name, k): names.append(k)
|
||||||
|
pass
|
||||||
|
if len(names) != 1:
|
||||||
|
raise ValueError("%d widget candidates match property '%s': %s" % \
|
||||||
|
(len(names), prop_name, names))
|
||||||
|
wid_name = names[0]
|
||||||
|
adapters = self.__create_adapters__(prop_name, wid_name)
|
||||||
|
pass
|
||||||
|
else: raise TypeError("Argument of adapt() must be an Adapter or a string")
|
||||||
|
|
||||||
|
else: # two arguments
|
||||||
|
if not (isinstance(args[0], types.StringType) and
|
||||||
|
isinstance(args[1], types.StringType)):
|
||||||
|
raise TypeError("Arguments of adapt() must be two strings")
|
||||||
|
|
||||||
|
# retrieves both property and widget, and creates an adapter
|
||||||
|
prop_name, wid_name = args
|
||||||
|
adapters = self.__create_adapters__(prop_name, wid_name)
|
||||||
|
pass
|
||||||
|
|
||||||
|
for ad in adapters: self.__adapters.append(ad)
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
def __match_prop_name(self, prop_name, wid_name):
|
||||||
|
"""
|
||||||
|
Used internally when searching for a suitable widget. To customize
|
||||||
|
its behaviour, re-implement this method into derived classes
|
||||||
|
"""
|
||||||
|
return wid_name.lower().endswith(prop_name.lower())
|
||||||
|
|
||||||
|
|
||||||
|
def __create_adapters__(self, prop_name, wid_name):
|
||||||
|
"""
|
||||||
|
Private service that looks at property and widgets types,
|
||||||
|
and possibly creates one or more (best) fitting adapters
|
||||||
|
that are returned as a list.
|
||||||
|
"""
|
||||||
|
from gtkmvc.adapters.basic import Adapter, RoUserClassAdapter
|
||||||
|
from gtkmvc.adapters.containers import StaticContainerAdapter
|
||||||
|
import gtk
|
||||||
|
|
||||||
|
res = []
|
||||||
|
|
||||||
|
wid = self.view[wid_name]
|
||||||
|
if wid is None: raise ValueError("Widget '%s' not found" % wid_name)
|
||||||
|
|
||||||
|
# Decides the type of adapters to be created.
|
||||||
|
if isinstance(wid, gtk.Calendar):
|
||||||
|
# calendar creates three adapter for year, month and day
|
||||||
|
ad = RoUserClassAdapter(self.model, prop_name,
|
||||||
|
lambda d: d.year, lambda d,y: d.replace(year=y))
|
||||||
|
ad.connect_widget(wid, lambda c: c.get_date()[0],
|
||||||
|
lambda c,y: c.select_month(c.get_date()[1], y),
|
||||||
|
"day-selected")
|
||||||
|
res.append(ad) # year
|
||||||
|
|
||||||
|
ad = RoUserClassAdapter(self.model, prop_name,
|
||||||
|
lambda d: d.month, lambda d,m: d.replace(month=m))
|
||||||
|
ad.connect_widget(wid, lambda c: c.get_date()[1]+1,
|
||||||
|
lambda c,m: c.select_month(m-1, c.get_date()[0]),
|
||||||
|
"day-selected")
|
||||||
|
res.append(ad) # month
|
||||||
|
|
||||||
|
ad = RoUserClassAdapter(self.model, prop_name,
|
||||||
|
lambda d: d.day, lambda d,v: d.replace(day=v))
|
||||||
|
ad.connect_widget(wid, lambda c: c.get_date()[2],
|
||||||
|
lambda c,d: c.select_day(d),
|
||||||
|
"day-selected")
|
||||||
|
res.append(ad) # day
|
||||||
|
return res
|
||||||
|
|
||||||
|
|
||||||
|
try: # tries with StaticContainerAdapter
|
||||||
|
ad = StaticContainerAdapter(self.model, prop_name)
|
||||||
|
ad.connect_widget(wid)
|
||||||
|
res.append(ad)
|
||||||
|
|
||||||
|
except TypeError:
|
||||||
|
# falls back to a simple adapter
|
||||||
|
ad = Adapter(self.model, prop_name)
|
||||||
|
ad.connect_widget(wid)
|
||||||
|
res.append(ad)
|
||||||
|
pass
|
||||||
|
|
||||||
|
return res
|
||||||
|
|
||||||
|
|
||||||
|
pass # end of class Controller
|
||||||
336
src/gtkmvc/model.py
Normal file
@@ -0,0 +1,336 @@
|
|||||||
|
# Author: Roberto Cavada <cavada@fbk.eu>
|
||||||
|
#
|
||||||
|
# Copyright (c) 2005 by Roberto Cavada
|
||||||
|
#
|
||||||
|
# pygtkmvc is free software; you can redistribute it and/or
|
||||||
|
# modify it under the terms of the GNU Lesser General Public
|
||||||
|
# License as published by the Free Software Foundation; either
|
||||||
|
# version 2 of the License, or (at your option) any later version.
|
||||||
|
#
|
||||||
|
# pygtkmvc 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
|
||||||
|
# Lesser General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU Lesser General Public
|
||||||
|
# License along with this library; if not, write to the Free
|
||||||
|
# Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
|
||||||
|
# Boston, MA 02110, USA.
|
||||||
|
#
|
||||||
|
# For more information on pygtkmvc see <http://pygtkmvc.sourceforge.net>
|
||||||
|
# or email to the author Roberto Cavada <cavada@fbk.eu>.
|
||||||
|
# Please report bugs to <cavada@fbk.eu>.
|
||||||
|
|
||||||
|
import support.metaclasses
|
||||||
|
from support.wrappers import ObsWrapperBase
|
||||||
|
from observable import Signal
|
||||||
|
|
||||||
|
|
||||||
|
class Model (object):
|
||||||
|
"""
|
||||||
|
This class is the application model base class. It handles a set
|
||||||
|
of observable properties which you are interested in showing by
|
||||||
|
one ore more view - via one or more observers of course. The
|
||||||
|
mechanism is the following:
|
||||||
|
|
||||||
|
1. You are interested in showing a set of model property, that
|
||||||
|
you can declare in the __properties__ member map.
|
||||||
|
|
||||||
|
2. You define one or more observers that observe one or more
|
||||||
|
properties you registered. When someone changes a property
|
||||||
|
value the model notifies the changing to each observer.
|
||||||
|
|
||||||
|
The property-observer[s] association is given by the implicit
|
||||||
|
rule in observers method names: if you want the model notified
|
||||||
|
the changing event of the value of the property 'p' you have to
|
||||||
|
define the method called 'property_p_value_change' in each
|
||||||
|
listening observer class.
|
||||||
|
|
||||||
|
Notice that tipically 'controllers' implement the observer
|
||||||
|
pattern. The notification method gets the emitting model, the
|
||||||
|
old value for the property and the new one. Properties
|
||||||
|
functionalities are automatically provided by the
|
||||||
|
ObservablePropertyMeta meta-class."""
|
||||||
|
|
||||||
|
__metaclass__ = support.metaclasses.ObservablePropertyMeta
|
||||||
|
__properties__ = {} # override this
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
object.__init__(self)
|
||||||
|
self.__observers = []
|
||||||
|
# keys are properties names, values are methods inside the observer:
|
||||||
|
self.__value_notifications = {}
|
||||||
|
self.__instance_notif_before = {}
|
||||||
|
self.__instance_notif_after = {}
|
||||||
|
self.__signal_notif = {}
|
||||||
|
|
||||||
|
for key in (self.__properties__.keys() + self.__derived_properties__.keys()):
|
||||||
|
self.register_property(key)
|
||||||
|
pass
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
|
def register_property(self, name):
|
||||||
|
"""Registers an existing property to be monitored, and sets
|
||||||
|
up notifiers for notifications"""
|
||||||
|
if not self.__value_notifications.has_key(name):
|
||||||
|
self.__value_notifications[name] = []
|
||||||
|
pass
|
||||||
|
|
||||||
|
# registers observable wrappers
|
||||||
|
prop = getattr(self, "_prop_%s" % name)
|
||||||
|
|
||||||
|
if isinstance(prop, ObsWrapperBase):
|
||||||
|
prop.__set_model__(self, name)
|
||||||
|
|
||||||
|
if isinstance(prop, Signal):
|
||||||
|
if not self.__signal_notif.has_key(name):
|
||||||
|
self.__signal_notif[name] = []
|
||||||
|
pass
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
if not self.__instance_notif_before.has_key(name):
|
||||||
|
self.__instance_notif_before[name] = []
|
||||||
|
pass
|
||||||
|
if not self.__instance_notif_after.has_key(name):
|
||||||
|
self.__instance_notif_after[name] = []
|
||||||
|
pass
|
||||||
|
pass
|
||||||
|
pass
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
def has_property(self, name):
|
||||||
|
"""Returns true if given property name refers an observable
|
||||||
|
property inside self or inside derived classes"""
|
||||||
|
return self.__properties__.has_key(name) or \
|
||||||
|
self.__derived_properties__.has_key(name)
|
||||||
|
|
||||||
|
|
||||||
|
def register_observer(self, observer):
|
||||||
|
if observer in self.__observers: return # not already registered
|
||||||
|
|
||||||
|
self.__observers.append(observer)
|
||||||
|
for key in (self.__properties__.keys() + self.__derived_properties__.keys()):
|
||||||
|
self.__add_observer_notification(observer, key)
|
||||||
|
pass
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
def unregister_observer(self, observer):
|
||||||
|
if observer not in self.__observers: return
|
||||||
|
|
||||||
|
for key in (self.__properties__.keys() + self.__derived_properties__.keys()):
|
||||||
|
self.__remove_observer_notification(observer, key)
|
||||||
|
pass
|
||||||
|
|
||||||
|
self.__observers.remove(observer)
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
def _reset_property_notification(self, prop_name):
|
||||||
|
"""Called when it has be done an assignment that changes the
|
||||||
|
type of a property or the instance of the property has been
|
||||||
|
changed to a different instance. In this case it must be
|
||||||
|
unregistered and registered again"""
|
||||||
|
|
||||||
|
self.register_property(prop_name)
|
||||||
|
|
||||||
|
for observer in self.__observers:
|
||||||
|
self.__remove_observer_notification(observer, prop_name)
|
||||||
|
self.__add_observer_notification(observer, prop_name)
|
||||||
|
pass
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
def __add_observer_notification(self, observer, prop_name):
|
||||||
|
"""Searches in the observer for any possible listener, and
|
||||||
|
stores the notification methods to be called later"""
|
||||||
|
|
||||||
|
method_name = "property_%s_value_change" % prop_name
|
||||||
|
if hasattr(observer, method_name):
|
||||||
|
method = getattr(observer, method_name)
|
||||||
|
if method not in self.__value_notifications[prop_name]:
|
||||||
|
list.append(self.__value_notifications[prop_name], method)
|
||||||
|
pass
|
||||||
|
pass
|
||||||
|
|
||||||
|
# is it a signal?
|
||||||
|
orig_prop = getattr(self, "_prop_%s" % prop_name)
|
||||||
|
if isinstance(orig_prop, Signal):
|
||||||
|
method_name = "property_%s_signal_emit" % prop_name
|
||||||
|
if hasattr(observer, method_name):
|
||||||
|
method = getattr(observer, method_name)
|
||||||
|
if method not in self.__signal_notif[prop_name]:
|
||||||
|
list.append(self.__signal_notif[prop_name], method)
|
||||||
|
pass
|
||||||
|
pass
|
||||||
|
pass
|
||||||
|
|
||||||
|
# is it an instance change notification type?
|
||||||
|
elif isinstance(orig_prop, ObsWrapperBase):
|
||||||
|
method_name = "property_%s_before_change" % prop_name
|
||||||
|
if hasattr(observer, method_name):
|
||||||
|
method = getattr(observer, method_name)
|
||||||
|
if method not in self.__instance_notif_before[prop_name]:
|
||||||
|
list.append(self.__instance_notif_before[prop_name], method)
|
||||||
|
pass
|
||||||
|
pass
|
||||||
|
|
||||||
|
method_name = "property_%s_after_change" % prop_name
|
||||||
|
if hasattr(observer, method_name):
|
||||||
|
method = getattr(observer, method_name)
|
||||||
|
if method not in self.__instance_notif_after[prop_name]:
|
||||||
|
list.append(self.__instance_notif_after[prop_name], method)
|
||||||
|
pass
|
||||||
|
pass
|
||||||
|
pass
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
def __remove_observer_notification(self, observer, prop_name):
|
||||||
|
if self.__value_notifications.has_key(prop_name):
|
||||||
|
method_name = "property_%s_value_change" % prop_name
|
||||||
|
if hasattr(observer, method_name):
|
||||||
|
method = getattr(observer, method_name)
|
||||||
|
if method in self.__value_notifications[prop_name]:
|
||||||
|
self.__value_notifications[prop_name].remove(method)
|
||||||
|
pass
|
||||||
|
pass
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
orig_prop = getattr(self, "_prop_%s" % prop_name)
|
||||||
|
# is it a signal?
|
||||||
|
if isinstance(orig_prop, Signal):
|
||||||
|
method_name = "property_%s_signal_emit" % prop_name
|
||||||
|
if hasattr(observer, method_name):
|
||||||
|
method = getattr(observer, method_name)
|
||||||
|
if method in self.__signal_notif[prop_name]:
|
||||||
|
self.__signal_notif[prop_name].remove(method)
|
||||||
|
pass
|
||||||
|
pass
|
||||||
|
pass
|
||||||
|
|
||||||
|
# is it an instance change notification type?
|
||||||
|
elif isinstance(orig_prop, ObsWrapperBase):
|
||||||
|
if self.__instance_notif_before.has_key(prop_name):
|
||||||
|
method_name = "property_%s_before_change" % prop_name
|
||||||
|
if hasattr(observer, method_name):
|
||||||
|
method = getattr(observer, method_name)
|
||||||
|
if method in self.__instance_notif_before[prop_name]:
|
||||||
|
self.__instance_notif_before[prop_name].remove(method)
|
||||||
|
pass
|
||||||
|
pass
|
||||||
|
pass
|
||||||
|
|
||||||
|
if self.__instance_notif_after.has_key(prop_name):
|
||||||
|
method_name = "property_%s_after_change" % prop_name
|
||||||
|
if hasattr(observer, method_name):
|
||||||
|
method = getattr(observer, method_name)
|
||||||
|
if method in self.__instance_notif_after[prop_name]:
|
||||||
|
self.__instance_notif_after[prop_name].remove(method)
|
||||||
|
pass
|
||||||
|
pass
|
||||||
|
pass
|
||||||
|
pass
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
def __notify_observer__(self, observer, method, *args, **kwargs):
|
||||||
|
"""This can be overridden by derived class in order to call
|
||||||
|
the method in a different manner (for example, in
|
||||||
|
multithreading, or a rpc, etc.) This implementation simply
|
||||||
|
calls the given method with the given arguments"""
|
||||||
|
return method(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
# ---------- Notifiers:
|
||||||
|
|
||||||
|
def notify_property_value_change(self, prop_name, old, new):
|
||||||
|
assert(self.__value_notifications.has_key(prop_name))
|
||||||
|
for method in self.__value_notifications[prop_name] :
|
||||||
|
obs = method.im_self
|
||||||
|
# notification occurs checking spuriousness of the observer
|
||||||
|
if old != new or obs.accepts_spurious_change():
|
||||||
|
self.__notify_observer__(obs, method,
|
||||||
|
self, old, new) # notifies the change
|
||||||
|
pass
|
||||||
|
pass
|
||||||
|
return
|
||||||
|
|
||||||
|
def notify_method_before_change(self, prop_name, instance, meth_name,
|
||||||
|
args, kwargs):
|
||||||
|
assert(self.__instance_notif_before.has_key(prop_name))
|
||||||
|
for method in self.__instance_notif_before[prop_name] :
|
||||||
|
self.__notify_observer__(method.im_self, method, self, instance,
|
||||||
|
meth_name, args, kwargs) # notifies the change
|
||||||
|
pass
|
||||||
|
return
|
||||||
|
|
||||||
|
def notify_method_after_change(self, prop_name, instance, meth_name,
|
||||||
|
res, args, kwargs):
|
||||||
|
assert(self.__instance_notif_after.has_key(prop_name))
|
||||||
|
for method in self.__instance_notif_after[prop_name] :
|
||||||
|
self.__notify_observer__(method.im_self, method, self, instance,
|
||||||
|
meth_name, res, args, kwargs) # notifies the change
|
||||||
|
pass
|
||||||
|
return
|
||||||
|
|
||||||
|
def notify_signal_emit(self, prop_name, args, kwargs):
|
||||||
|
assert(self.__signal_notif.has_key(prop_name))
|
||||||
|
for method in self.__signal_notif[prop_name] :
|
||||||
|
self.__notify_observer__(method.im_self, method, self,
|
||||||
|
args, kwargs) # notifies the signal emit
|
||||||
|
pass
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
pass # end of class Model
|
||||||
|
# ----------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
import gtk
|
||||||
|
# ----------------------------------------------------------------------
|
||||||
|
class TreeStoreModel (Model, gtk.TreeStore):
|
||||||
|
"""Use this class as base class for your model derived by
|
||||||
|
gtk.TreeStore"""
|
||||||
|
__metaclass__ = support.metaclasses.ObservablePropertyGObjectMeta
|
||||||
|
|
||||||
|
def __init__(self, column_type, *args):
|
||||||
|
Model.__init__(self)
|
||||||
|
gtk.TreeStore.__init__(self, column_type, *args)
|
||||||
|
return
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------
|
||||||
|
class ListStoreModel (Model, gtk.ListStore):
|
||||||
|
"""Use this class as base class for your model derived by
|
||||||
|
gtk.ListStore"""
|
||||||
|
__metaclass__ = support.metaclasses.ObservablePropertyGObjectMeta
|
||||||
|
|
||||||
|
def __init__(self, column_type, *args):
|
||||||
|
Model.__init__(self)
|
||||||
|
gtk.ListStore.__init__(self, column_type, *args)
|
||||||
|
return
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------
|
||||||
|
class TextBufferModel (Model, gtk.TextBuffer):
|
||||||
|
"""Use this class as base class for your model derived by
|
||||||
|
gtk.TextBuffer"""
|
||||||
|
__metaclass__ = support.metaclasses.ObservablePropertyGObjectMeta
|
||||||
|
|
||||||
|
def __init__(self, table=None):
|
||||||
|
Model.__init__(self)
|
||||||
|
gtk.TextBuffer.__init__(self, table)
|
||||||
|
return
|
||||||
|
pass
|
||||||
|
|
||||||
124
src/gtkmvc/model_mt.py
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
# Author: Roberto Cavada <cavada@fbk.eu>
|
||||||
|
#
|
||||||
|
# Copyright (c) 2006 by Roberto Cavada
|
||||||
|
#
|
||||||
|
# pygtkmvc is free software; you can redistribute it and/or
|
||||||
|
# modify it under the terms of the GNU Lesser General Public
|
||||||
|
# License as published by the Free Software Foundation; either
|
||||||
|
# version 2 of the License, or (at your option) any later version.
|
||||||
|
#
|
||||||
|
# pygtkmvc 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
|
||||||
|
# Lesser General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU Lesser General Public
|
||||||
|
# License along with this library; if not, write to the Free Software
|
||||||
|
# Foundation, Inc., 51 Franklin Street, Fifth Floor,
|
||||||
|
# Boston, MA 02110, USA.
|
||||||
|
#
|
||||||
|
# For more information on pygtkmvc see <http://pygtkmvc.sourceforge.net>
|
||||||
|
# or email to the author Roberto Cavada <cavada@fbk.eu>.
|
||||||
|
# Please report bugs to <cavada@fbk.eu>.
|
||||||
|
|
||||||
|
|
||||||
|
from gtkmvc.model import Model
|
||||||
|
import support.metaclasses
|
||||||
|
|
||||||
|
try: import threading as _threading
|
||||||
|
except ImportError: import dummy_threading as _threading
|
||||||
|
|
||||||
|
import gobject
|
||||||
|
if hasattr(gobject, "threads_init"): gobject.threads_init()
|
||||||
|
else: import gtk; gtk.threads_init()
|
||||||
|
|
||||||
|
|
||||||
|
class ModelMT (Model):
|
||||||
|
"""A base class for models whose observable properties can be
|
||||||
|
changed by threads different than gtk main thread. Notification is
|
||||||
|
performed by exploiting the gtk idle loop only if needed,
|
||||||
|
otherwise the standard notification system (direct method call) is
|
||||||
|
used. In this model, the observer is expected to run in the gtk
|
||||||
|
main loop thread."""
|
||||||
|
|
||||||
|
__metaclass__ = support.metaclasses.ObservablePropertyMetaMT
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
Model.__init__(self)
|
||||||
|
self.__observer_threads = {}
|
||||||
|
self._prop_lock = _threading.Lock()
|
||||||
|
return
|
||||||
|
|
||||||
|
def register_observer(self, observer):
|
||||||
|
Model.register_observer(self, observer)
|
||||||
|
self.__observer_threads[observer] = _threading.currentThread()
|
||||||
|
return
|
||||||
|
|
||||||
|
def unregister_observer(self, observer):
|
||||||
|
Model.unregister_observer(self, observer)
|
||||||
|
del self.__observer_threads[observer]
|
||||||
|
return
|
||||||
|
|
||||||
|
# ---------- Notifiers:
|
||||||
|
|
||||||
|
def __notify_observer__(self, observer, method, *args, **kwargs):
|
||||||
|
"""This makes a call either through the gtk.idle list or a
|
||||||
|
direct method call depending whether the caller's thread is
|
||||||
|
different from the observer's thread"""
|
||||||
|
|
||||||
|
assert self.__observer_threads.has_key(observer)
|
||||||
|
if _threading.currentThread() == self.__observer_threads[observer]:
|
||||||
|
# standard call
|
||||||
|
return Model.__notify_observer__(self, observer, method,
|
||||||
|
*args, **kwargs)
|
||||||
|
|
||||||
|
# multi-threading call
|
||||||
|
gobject.idle_add(self.__idle_callback, observer, method, args, kwargs)
|
||||||
|
return
|
||||||
|
|
||||||
|
def __idle_callback(self, observer, method, args, kwargs):
|
||||||
|
method(*args, **kwargs)
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
pass # end of class
|
||||||
|
|
||||||
|
|
||||||
|
import gtk
|
||||||
|
# ----------------------------------------------------------------------
|
||||||
|
class TreeStoreModelMT (ModelMT, gtk.TreeStore):
|
||||||
|
"""Use this class as base class for your model derived by
|
||||||
|
gtk.TreeStore"""
|
||||||
|
__metaclass__ = support.metaclasses.ObservablePropertyGObjectMetaMT
|
||||||
|
|
||||||
|
def __init__(self, column_type, *args):
|
||||||
|
ModelMT.__init__(self)
|
||||||
|
gtk.TreeStore.__init__(self, column_type, *args)
|
||||||
|
return
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------
|
||||||
|
class ListStoreModelMT (ModelMT, gtk.ListStore):
|
||||||
|
"""Use this class as base class for your model derived by
|
||||||
|
gtk.ListStore"""
|
||||||
|
__metaclass__ = support.metaclasses.ObservablePropertyGObjectMetaMT
|
||||||
|
|
||||||
|
def __init__(self, column_type, *args):
|
||||||
|
ModelMT.__init__(self)
|
||||||
|
gtk.ListStore.__init__(self, column_type, *args)
|
||||||
|
return
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------
|
||||||
|
class TextBufferModelMT (ModelMT, gtk.TextBuffer):
|
||||||
|
"""Use this class as base class for your model derived by
|
||||||
|
gtk.TextBuffer"""
|
||||||
|
__metaclass__ = support.metaclasses.ObservablePropertyGObjectMetaMT
|
||||||
|
|
||||||
|
def __init__(self, table=None):
|
||||||
|
ModelMT.__init__(self)
|
||||||
|
gtk.TextBuffer.__init__(self, table)
|
||||||
|
return
|
||||||
|
pass
|
||||||
69
src/gtkmvc/observable.py
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
# -------------------------------------------------------------------------
|
||||||
|
# Author: Roberto Cavada <cavada@fbk.eu>
|
||||||
|
#
|
||||||
|
# Copyright (C) 2006 by Roberto Cavada
|
||||||
|
#
|
||||||
|
# pygtkmvc is free software; you can redistribute it and/or
|
||||||
|
# modify it under the terms of the GNU Lesser General Public
|
||||||
|
# License as published by the Free Software Foundation; either
|
||||||
|
# version 2 of the License, or (at your option) any later version.
|
||||||
|
#
|
||||||
|
# pygtkmvc 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
|
||||||
|
# Lesser General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU Lesser General Public
|
||||||
|
# License along with this library; if not, write to the Free Software
|
||||||
|
# 51 Franklin Street, Fifth Floor,
|
||||||
|
# Boston, MA 02110, USA.ridge, MA 02139, USA.
|
||||||
|
#
|
||||||
|
# For more information on pygtkmvc see <http://pygtkmvc.sourceforge.net>
|
||||||
|
# or email to the author <cavada@fbk.eu>.
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
from support import decorators
|
||||||
|
from support.wrappers import ObsWrapperBase
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------
|
||||||
|
class Observable (ObsWrapperBase):
|
||||||
|
def __init__(self):
|
||||||
|
ObsWrapperBase.__init__(self)
|
||||||
|
return
|
||||||
|
pass # end of class
|
||||||
|
|
||||||
|
|
||||||
|
@decorators.good_decorator
|
||||||
|
def observed(func):
|
||||||
|
"""Use this decorator to make your class methods observable.
|
||||||
|
|
||||||
|
Your observer will receive at most two notifications:
|
||||||
|
- property_<name>_before_change
|
||||||
|
- property_<name>_after_change
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
def wrapper(*args, **kwargs):
|
||||||
|
self = args[0]
|
||||||
|
assert(isinstance(self, Observable))
|
||||||
|
|
||||||
|
self._notify_method_before(self, func.__name__, args, kwargs)
|
||||||
|
res = func(*args, **kwargs)
|
||||||
|
self._notify_method_after(self, func.__name__, res, args, kwargs)
|
||||||
|
return res
|
||||||
|
return wrapper
|
||||||
|
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------
|
||||||
|
class Signal (Observable):
|
||||||
|
"""Base class for signals properties"""
|
||||||
|
def __init__(self):
|
||||||
|
Observable.__init__(self)
|
||||||
|
return
|
||||||
|
|
||||||
|
def emit(self, *args, **kwargs):
|
||||||
|
return self.__get_model__().notify_signal_emit(
|
||||||
|
self.__get_prop_name__(), args, kwargs)
|
||||||
|
pass # end of class
|
||||||
|
|
||||||
91
src/gtkmvc/observer.py
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
# -------------------------------------------------------------------------
|
||||||
|
# Author: Roberto Cavada <cavada@fbk.eu>
|
||||||
|
#
|
||||||
|
# Copyright (C) 2006 by Roberto Cavada
|
||||||
|
#
|
||||||
|
# pygtkmvc is free software; you can redistribute it and/or
|
||||||
|
# modify it under the terms of the GNU Lesser General Public
|
||||||
|
# License as published by the Free Software Foundation; either
|
||||||
|
# version 2 of the License, or (at your option) any later version.
|
||||||
|
#
|
||||||
|
# pygtkmvc 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
|
||||||
|
# Lesser General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU Lesser General Public
|
||||||
|
# License along with this library; if not, write to the Free Software
|
||||||
|
# Foundation, Inc., 51 Franklin Street, Fifth Floor,
|
||||||
|
# Boston, MA 02110, USA.
|
||||||
|
#
|
||||||
|
# For more information on pygtkmvc see <http://pygtkmvc.sourceforge.net>
|
||||||
|
# or email to the author Roberto Cavada <cavada@fbk.eu>.
|
||||||
|
# Please report bugs to <cavada@fbk.eu>.
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
class Observer (object):
|
||||||
|
"""Use this class as base class of all observers"""
|
||||||
|
|
||||||
|
def __init__(self, model=None, spurious=False):
|
||||||
|
"""
|
||||||
|
When parameter spurious is set to False
|
||||||
|
(default value) the observer declares that it is not
|
||||||
|
interested in receiving value-change notifications when
|
||||||
|
property's value does not really change. This happens when a
|
||||||
|
property got assigned to a value that is the same it had
|
||||||
|
before being assigned.
|
||||||
|
|
||||||
|
A notification was used to be sent to the observer even in
|
||||||
|
this particular condition, because spurious (non-changing)
|
||||||
|
assignments were used as signals when signals were not
|
||||||
|
supported by early version of the framework. The observer
|
||||||
|
was in charge of deciding what to do with spurious
|
||||||
|
assignments, by checking if the old and new values were
|
||||||
|
different at the beginning of the notification code. With
|
||||||
|
latest version providing new notification types like
|
||||||
|
signals, this requirement seems to be no longer needed, and
|
||||||
|
delivering a notification is no longer a sensible
|
||||||
|
behaviour.
|
||||||
|
|
||||||
|
This is the reason for providing parameter
|
||||||
|
spurious that changes the previous behaviour
|
||||||
|
but keeps availability of a possible backward compatible
|
||||||
|
feature.
|
||||||
|
"""
|
||||||
|
|
||||||
|
self.model = None
|
||||||
|
self.__accepts_spurious__ = spurious
|
||||||
|
self.register_model(model)
|
||||||
|
return
|
||||||
|
|
||||||
|
def register_model(self, model):
|
||||||
|
self.unregister_model()
|
||||||
|
self.model = model
|
||||||
|
if self.model: self.model.register_observer(self)
|
||||||
|
return
|
||||||
|
|
||||||
|
def accepts_spurious_change(self):
|
||||||
|
"""
|
||||||
|
Returns True if this observer is interested in receiving
|
||||||
|
spurious value changes. This is queried by the model when
|
||||||
|
notifying a value change."""
|
||||||
|
return self.__accepts_spurious__
|
||||||
|
|
||||||
|
def unregister_model(self):
|
||||||
|
if self.model:
|
||||||
|
self.model.unregister_observer(self)
|
||||||
|
self.model = None
|
||||||
|
pass
|
||||||
|
return
|
||||||
|
|
||||||
|
def __del__(self):
|
||||||
|
self.unregister_model()
|
||||||
|
return
|
||||||
|
|
||||||
|
def get_model(self): return self.model
|
||||||
|
|
||||||
|
pass # end of class
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
26
src/gtkmvc/support/__init__.py
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
# Author: Roberto Cavada <cavada@fbk.eu>
|
||||||
|
#
|
||||||
|
# Copyright (c) 2005 by Roberto Cavada
|
||||||
|
#
|
||||||
|
# pygtkmvc is free software; you can redistribute it and/or
|
||||||
|
# modify it under the terms of the GNU Lesser General Public
|
||||||
|
# License as published by the Free Software Foundation; either
|
||||||
|
# version 2 of the License, or (at your option) any later version.
|
||||||
|
#
|
||||||
|
# pygtkmvc 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
|
||||||
|
# Lesser General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU Lesser General Public
|
||||||
|
# License along with this library; if not, write to the Free Software
|
||||||
|
# Foundation, Inc., 51 Franklin Street, Fifth Floor,
|
||||||
|
# Boston, MA 02110, USA.
|
||||||
|
#
|
||||||
|
# For more information on pygtkmvc see <http://pygtkmvc.sourceforge.net>
|
||||||
|
# or email to the author Roberto Cavada <cavada@fbk.eu>.
|
||||||
|
# Please report bugs to <cavada@fbk.eu>.
|
||||||
|
|
||||||
|
|
||||||
|
__all__ = ["metaclass_base", "metaclasses", "wrappers", "decorators",
|
||||||
|
"factories"]
|
||||||
44
src/gtkmvc/support/decorators.py
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
# -------------------------------------------------------------------------
|
||||||
|
# Author: Roberto Cavada <cavada@fbk.eu>
|
||||||
|
#
|
||||||
|
# Copyright (C) 2006 by Roberto Cavada
|
||||||
|
#
|
||||||
|
# pygtkmvc is free software; you can redistribute it and/or
|
||||||
|
# modify it under the terms of the GNU Lesser General Public
|
||||||
|
# License as published by the Free Software Foundation; either
|
||||||
|
# version 2 of the License, or (at your option) any later version.
|
||||||
|
#
|
||||||
|
# pygtkmvc 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
|
||||||
|
# Lesser General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU Lesser General Public
|
||||||
|
# License along with this library; if not, write to the Free Software
|
||||||
|
# Foundation, Inc., 51 Franklin Street, Fifth Floor,
|
||||||
|
# Boston, MA 02110, USA.
|
||||||
|
#
|
||||||
|
# For more information on pygtkmvc see <http://pygtkmvc.sourceforge.net>
|
||||||
|
# or email to the author Roberto Cavada <cavada@fbk.eu>.
|
||||||
|
# Please report bugs to <cavada@fbk.eu>.
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# This file contains decorators to be used (privately) by other parts
|
||||||
|
# of the framework
|
||||||
|
|
||||||
|
def good_decorator(decorator):
|
||||||
|
"""This decorator makes decorators behave well wrt to decorated
|
||||||
|
functions names, doc, etc."""
|
||||||
|
def new_decorator(f):
|
||||||
|
g = decorator(f)
|
||||||
|
g.__name__ = f.__name__
|
||||||
|
g.__doc__ = f.__doc__
|
||||||
|
g.__dict__.update(f.__dict__)
|
||||||
|
return g
|
||||||
|
|
||||||
|
new_decorator.__name__ = decorator.__name__
|
||||||
|
new_decorator.__doc__ = decorator.__doc__
|
||||||
|
new_decorator.__dict__.update(decorator.__dict__)
|
||||||
|
return new_decorator
|
||||||
24
src/gtkmvc/support/exceptions.py
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
# Author: Roberto Cavada <cavada@fbk.eu>
|
||||||
|
#
|
||||||
|
# Copyright (c) 2005 by Roberto Cavada
|
||||||
|
#
|
||||||
|
# pygtkmvc is free software; you can redistribute it and/or
|
||||||
|
# modify it under the terms of the GNU Lesser General Public
|
||||||
|
# License as published by the Free Software Foundation; either
|
||||||
|
# version 2 of the License, or (at your option) any later version.
|
||||||
|
#
|
||||||
|
# pygtkmvc 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
|
||||||
|
# Lesser General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU Lesser General Public
|
||||||
|
# License along with this library; if not, write to the Free Software
|
||||||
|
# Foundation, Inc., 51 Franklin Street, Fifth Floor,
|
||||||
|
# Boston, MA 02110, USA.
|
||||||
|
#
|
||||||
|
# For more information on pygtkmvc see <http://pygtkmvc.sourceforge.net>
|
||||||
|
# or email to the author Roberto Cavada <cavada@fbk.eu>.
|
||||||
|
# Please report bugs to <cavada@fbk.eu>.
|
||||||
|
|
||||||
|
|
||||||
86
src/gtkmvc/support/factories.py
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
# -------------------------------------------------------------------------
|
||||||
|
# Author: Roberto Cavada <cavada@fbk.eu>
|
||||||
|
#
|
||||||
|
# Copyright (C) 2008 by Roberto Cavada
|
||||||
|
#
|
||||||
|
# pygtkmvc is free software; you can redistribute it and/or
|
||||||
|
# modify it under the terms of the GNU Lesser General Public
|
||||||
|
# License as published by the Free Software Foundation; either
|
||||||
|
# version 2 of the License, or (at your option) any later version.
|
||||||
|
#
|
||||||
|
# pygtkmvc 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
|
||||||
|
# Lesser General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU Lesser General Public
|
||||||
|
# License along with this library; if not, write to the Free Software
|
||||||
|
# Foundation, Inc., 51 Franklin Street, Fifth Floor,
|
||||||
|
# Boston, MA 02110, USA.
|
||||||
|
#
|
||||||
|
# For more information on pygtkmvc see <http://pygtkmvc.sourceforge.net>
|
||||||
|
# or email to the author Roberto Cavada <cavada@fbk.eu>.
|
||||||
|
# Please report bugs to <cavada@fbk.eu>.
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
|
||||||
|
import new
|
||||||
|
from gtkmvc import Model
|
||||||
|
from noconflict import get_noconflict_metaclass
|
||||||
|
|
||||||
|
class ModelFactory (object):
|
||||||
|
"""This factory constructs classes for models. Use it to build
|
||||||
|
the classes to derive your own models"""
|
||||||
|
|
||||||
|
__memoized = {}
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def __fix_bases(base_classes, have_mt):
|
||||||
|
"""This function check whether base_classes contains a Model
|
||||||
|
instance. If not, choose the best fitting class for
|
||||||
|
model. Furthermore, it makes the list in a cannonical
|
||||||
|
ordering form in a way that ic can be used as memoization
|
||||||
|
key"""
|
||||||
|
fixed = list(base_classes)
|
||||||
|
contains_model = False
|
||||||
|
for b in fixed:
|
||||||
|
if isinstance(fixed, Model): contains_model = True; break
|
||||||
|
pass
|
||||||
|
|
||||||
|
# adds a model when user is lazy
|
||||||
|
if not contains_model:
|
||||||
|
if have_mt:
|
||||||
|
from gtkmvc.model_mt import ModelMT
|
||||||
|
fixed.insert(0, ModelMT)
|
||||||
|
else: fixed.insert(0, Model)
|
||||||
|
pass
|
||||||
|
|
||||||
|
class ModelFactoryWrap (object):
|
||||||
|
__metaclass__ = get_noconflict_metaclass(tuple(fixed), (), ())
|
||||||
|
def __init__(self, *args, **kwargs): pass
|
||||||
|
pass
|
||||||
|
|
||||||
|
fixed.append(ModelFactoryWrap)
|
||||||
|
fixed.sort()
|
||||||
|
return tuple(fixed)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def make(base_classes=(), have_mt=False):
|
||||||
|
"""Use this static method to build a model class that
|
||||||
|
possibly derives from other classes. If have_mt is True,
|
||||||
|
then returned class will take into account multi-threading
|
||||||
|
issues when dealing with observable properties."""
|
||||||
|
|
||||||
|
good_bc = ModelFactory.__fix_bases(base_classes, have_mt)
|
||||||
|
print "Base classes are:", good_bc
|
||||||
|
key = "".join(map(str, good_bc))
|
||||||
|
if ModelFactory.__memoized.has_key(key):
|
||||||
|
return ModelFactory.__memoized[key]
|
||||||
|
|
||||||
|
cls = new.classobj('', good_bc, {'__module__': '__main__', '__doc__': None})
|
||||||
|
ModelFactory.__memoized[key] = cls
|
||||||
|
return cls
|
||||||
|
|
||||||
|
#__
|
||||||
|
#make = staticmethod(make)
|
||||||
|
|
||||||
|
pass # end of class
|
||||||
240
src/gtkmvc/support/metaclass_base.py
Normal file
@@ -0,0 +1,240 @@
|
|||||||
|
# Author: Roberto Cavada <cavada@fbk.eu>
|
||||||
|
#
|
||||||
|
# Copyright (c) 2005 by Roberto Cavada
|
||||||
|
#
|
||||||
|
# pygtkmvc is free software; you can redistribute it and/or
|
||||||
|
# modify it under the terms of the GNU Lesser General Public
|
||||||
|
# License as published by the Free Software Foundation; either
|
||||||
|
# version 2 of the License, or (at your option) any later version.
|
||||||
|
#
|
||||||
|
# pygtkmvc 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
|
||||||
|
# Lesser General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU Lesser General Public
|
||||||
|
# License along with this library; if not, write to the Free Software
|
||||||
|
# Foundation, Inc., 51 Franklin Street, Fifth Floor,
|
||||||
|
# Boston, MA 02110, USA.
|
||||||
|
#
|
||||||
|
# For more information on pygtkmvc see <http://pygtkmvc.sourceforge.net>
|
||||||
|
# or email to the author Roberto Cavada <cavada@fbk.eu>.
|
||||||
|
# Please report bugs to <cavada@fbk.eu>.
|
||||||
|
|
||||||
|
|
||||||
|
import new
|
||||||
|
import re
|
||||||
|
import types
|
||||||
|
|
||||||
|
import gtkmvc.support.wrappers as wrappers
|
||||||
|
from gtkmvc.support.utils import get_function_from_source
|
||||||
|
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------
|
||||||
|
|
||||||
|
VERBOSE_LEVEL = 5
|
||||||
|
|
||||||
|
class PropertyMeta (type):
|
||||||
|
"""This is a meta-class that provides auto-property support.
|
||||||
|
The idea is to allow programmers to define some properties which
|
||||||
|
will be automatically connected to auto-generated code which handles
|
||||||
|
access to those properties.
|
||||||
|
How can you use this meta-class?
|
||||||
|
First, '__metaclass__ = PropertyMeta' must be class member of the class
|
||||||
|
you want to make the automatic properties handling.
|
||||||
|
Second, '__properties__' must be a map containing the properties names
|
||||||
|
as keys, values will be initial values for properties.
|
||||||
|
That's all: after the instantiation, your class will contain all properties
|
||||||
|
you named inside '__properties__'. Each of them will be also associated
|
||||||
|
to a couple of automatically-generated functions which get and set the
|
||||||
|
property value inside a generated member variable.
|
||||||
|
About names: suppose the property is called 'x'. The generated variable
|
||||||
|
(which keeps the real value of the property x) is called _prop_x.
|
||||||
|
The getter is called get_prop_x(self), and the setter is called
|
||||||
|
'set_prop_x(self, value)'.
|
||||||
|
|
||||||
|
Customization:
|
||||||
|
The base implementation of getter is to return the value stored in the
|
||||||
|
variable associated to the property. The setter simply sets its value.
|
||||||
|
Programmers can override basic behaviour for getters or setters simply by
|
||||||
|
defining their getters and setters (see at the names convention above).
|
||||||
|
The customized function can lie everywhere in the user classes hierarchy.
|
||||||
|
Every overridden function will not be generated by the metaclass.
|
||||||
|
|
||||||
|
To supply your own methods is good for few methods, but can result in a
|
||||||
|
very unconfortable way for many methods. In this case you can extend
|
||||||
|
the meta-class, and override methods get_[gs]etter_source with your
|
||||||
|
implementation (this can be probably made better).
|
||||||
|
An example is provided in meta-class PropertyMetaVerbose below.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(cls, name, bases, dict):
|
||||||
|
"""class constructor"""
|
||||||
|
properties = {}
|
||||||
|
type.__init__(cls, name, bases, dict)
|
||||||
|
|
||||||
|
props = getattr(cls, '__properties__', {})
|
||||||
|
setattr(cls, '__derived_properties__', {})
|
||||||
|
der_props = getattr(cls, '__derived_properties__')
|
||||||
|
|
||||||
|
# Calculates derived properties:
|
||||||
|
for base in bases:
|
||||||
|
maps = ( getattr(base, '__properties__', {}),
|
||||||
|
getattr(base, '__derived_properties__', {}) )
|
||||||
|
for _map in maps:
|
||||||
|
for p in _map.keys():
|
||||||
|
if not props.has_key(p) and not der_props.has_key(p):
|
||||||
|
der_props[p] = _map[p]
|
||||||
|
pass
|
||||||
|
pass
|
||||||
|
pass
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Generates code for all properties (but not for derived props):
|
||||||
|
props = getattr(cls, '__properties__', {})
|
||||||
|
for prop in props.keys():
|
||||||
|
type(cls).__create_prop_accessors__(cls, prop, props[prop])
|
||||||
|
pass
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
def __msg__(cls, msg, level):
|
||||||
|
"""if level is less or equal to VERBOSE_LEVEL, ths message will
|
||||||
|
be printed"""
|
||||||
|
if level <= VERBOSE_LEVEL: print msg
|
||||||
|
return
|
||||||
|
|
||||||
|
def __create_prop_accessors__(cls, prop_name, default_val):
|
||||||
|
"""Private method that creates getter and setter, and the
|
||||||
|
corresponding property"""
|
||||||
|
getter_name = "get_prop_%s" % prop_name
|
||||||
|
setter_name = "set_prop_%s" % prop_name
|
||||||
|
|
||||||
|
members_names = cls.__dict__.keys()
|
||||||
|
|
||||||
|
# checks if accessors are already defined:
|
||||||
|
if getter_name not in members_names:
|
||||||
|
src = type(cls).get_getter_source(cls, getter_name, prop_name)
|
||||||
|
func = get_function_from_source(src)
|
||||||
|
setattr(cls, getter_name, func)
|
||||||
|
else:
|
||||||
|
cls.__msg__("Warning: Custom member '%s' overloads generated accessor of property '%s'" \
|
||||||
|
% (getter_name, prop_name), 2)
|
||||||
|
pass
|
||||||
|
|
||||||
|
if setter_name not in members_names:
|
||||||
|
src = type(cls).get_setter_source(cls, setter_name, prop_name)
|
||||||
|
func = get_function_from_source(src)
|
||||||
|
setattr(cls, setter_name, func)
|
||||||
|
else:
|
||||||
|
cls.__msg__("Warning: Custom member '%s' overloads generated accessor of property '%s'" \
|
||||||
|
% (setter_name, prop_name), 2)
|
||||||
|
pass
|
||||||
|
|
||||||
|
prop = property(getattr(cls, getter_name), getattr(cls, setter_name))
|
||||||
|
|
||||||
|
if prop_name in members_names:
|
||||||
|
cls.__msg__("Warning: automatic property builder overrids property %s in class %s" \
|
||||||
|
% (prop_name, cls.__name__), 2)
|
||||||
|
pass
|
||||||
|
setattr(cls, prop_name, prop)
|
||||||
|
|
||||||
|
varname = "_prop_%s" % prop_name
|
||||||
|
if not varname in members_names: cls.__create_property(varname, default_val)
|
||||||
|
else: cls.__msg__("Warning: automatic property builder found a possible clashing for variable %s inside class %s" \
|
||||||
|
% (varname, cls.__name__), 2)
|
||||||
|
return
|
||||||
|
|
||||||
|
def __create_property(cls, name, default_val):
|
||||||
|
setattr(cls, name, cls.create_value(name, default_val))
|
||||||
|
return
|
||||||
|
|
||||||
|
def check_value_change(cls, old, new):
|
||||||
|
"""Checks whether the value of the property changed in type
|
||||||
|
or if the instance has been changed to a different instance.
|
||||||
|
If true, a call to model._reset_property_notification should
|
||||||
|
be called in order to re-register the new property instance
|
||||||
|
or type"""
|
||||||
|
return type(old) != type(new) or \
|
||||||
|
isinstance(old, wrappers.ObsWrapperBase) and (old != new)
|
||||||
|
|
||||||
|
def create_value(cls, prop_name, val, model=None):
|
||||||
|
"""This is used to create a value to be assigned to a
|
||||||
|
property. Depending on the type of the value, different values
|
||||||
|
are created and returned. For example, for a list, a
|
||||||
|
ListWrapper is created to wrap it, and returned for the
|
||||||
|
assignment. model is different from model when the value is
|
||||||
|
changed (a model exists). Otherwise, during property creation
|
||||||
|
model is None"""
|
||||||
|
|
||||||
|
if isinstance(val, tuple):
|
||||||
|
# this might be a class instance to be wrapped
|
||||||
|
if len(val) == 3 and \
|
||||||
|
isinstance(val[1], val[0]) and \
|
||||||
|
(isinstance(val[2], tuple) or isinstance(val[2], list)):
|
||||||
|
res = wrappers.ObsUserClassWrapper(val[1], val[2])
|
||||||
|
if model: res.__set_model__(model, prop_name)
|
||||||
|
return res
|
||||||
|
pass
|
||||||
|
|
||||||
|
elif isinstance(val, list):
|
||||||
|
res = wrappers.ObsListWrapper(val)
|
||||||
|
if model: res.__set_model__(model, prop_name)
|
||||||
|
return res
|
||||||
|
|
||||||
|
elif isinstance(val, dict):
|
||||||
|
res = wrappers.ObsMapWrapper(val)
|
||||||
|
if model: res.__set_model__(model, prop_name)
|
||||||
|
return res
|
||||||
|
|
||||||
|
return val
|
||||||
|
|
||||||
|
|
||||||
|
# ------------------------------------------------------------
|
||||||
|
# Services
|
||||||
|
# ------------------------------------------------------------
|
||||||
|
|
||||||
|
# Override these:
|
||||||
|
def get_getter_source(cls, getter_name, prop_name):
|
||||||
|
"""This must be overridden if you need a different implementation.
|
||||||
|
Simply the generated implementation returns the variable name
|
||||||
|
_prop_name"""
|
||||||
|
|
||||||
|
return "def %s(self): return self._prop_%s" % (getter_name, prop_name)
|
||||||
|
|
||||||
|
def get_setter_source(cls, setter_name, prop_name):
|
||||||
|
"""This must be overridden if you need a different implementation.
|
||||||
|
Simply the generated implementation sets the variable _prop_name"""
|
||||||
|
return "def %s(self, val): self._prop_%s = val" \
|
||||||
|
% (setter_name, prop_name)
|
||||||
|
|
||||||
|
pass # end of class
|
||||||
|
# ----------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
# What follows underneath is a set of examples of usage
|
||||||
|
|
||||||
|
## class PropertyMetaVerbose (PropertyMeta):
|
||||||
|
## """An example of customization"""
|
||||||
|
## def get_getter_source(cls, getter_name, prop_name):
|
||||||
|
## return "def %s(self): print 'Calling %s!'; return self._prop_%s" \
|
||||||
|
## % (getter_name, getter_name, prop_name)
|
||||||
|
|
||||||
|
## def get_setter_source(cls, setter_name, prop_name):
|
||||||
|
## return "def %s(self, val): print 'Calling %s!'; self._prop_%s = val;" \
|
||||||
|
## % (setter_name, setter_name, prop_name)
|
||||||
|
## pass #end of class
|
||||||
|
# ----------------------------------------------------------------------
|
||||||
|
|
||||||
|
## class User:
|
||||||
|
## """An example of usage"""
|
||||||
|
## __metaclass__ = PropertyMetaVerbose
|
||||||
|
## __properties__ = {'x':10, 'y':20}
|
||||||
|
|
||||||
|
## def __init__(self):
|
||||||
|
## print self.x # x is 10
|
||||||
|
## self.x = self.y + 10 # x is now 30
|
||||||
|
## return
|
||||||
|
## pass
|
||||||
|
# ----------------------------------------------------------------------
|
||||||
82
src/gtkmvc/support/metaclasses.py
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
# Author: Roberto Cavada <cavada@fbk.eu>
|
||||||
|
#
|
||||||
|
# Copyright (c) 2005 by Roberto Cavada
|
||||||
|
#
|
||||||
|
# pygtkmvc is free software; you can redistribute it and/or
|
||||||
|
# modify it under the terms of the GNU Lesser General Public
|
||||||
|
# License as published by the Free Software Foundation; either
|
||||||
|
# version 2 of the License, or (at your option) any later version.
|
||||||
|
#
|
||||||
|
# pygtkmvc 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
|
||||||
|
# Lesser General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU Lesser General Public
|
||||||
|
# License along with this library; if not, write to the Free Software
|
||||||
|
# Foundation, Inc., 51 Franklin Street, Fifth Floor,
|
||||||
|
# Boston, MA 02110, USA.
|
||||||
|
#
|
||||||
|
# For more information on pygtkmvc see <http://pygtkmvc.sourceforge.net>
|
||||||
|
# or email to the author Roberto Cavada <cavada@fbk.eu>.
|
||||||
|
# Please report bugs to <cavada@fbk.eu>.
|
||||||
|
|
||||||
|
from metaclass_base import PropertyMeta
|
||||||
|
import types
|
||||||
|
|
||||||
|
|
||||||
|
class ObservablePropertyMeta (PropertyMeta):
|
||||||
|
"""Classes instantiated by this meta-class must provide a method named
|
||||||
|
notify_property_change(self, prop_name, old, new)"""
|
||||||
|
def __init__(cls, name, bases, dict):
|
||||||
|
PropertyMeta.__init__(cls, name, bases, dict)
|
||||||
|
return
|
||||||
|
|
||||||
|
def get_setter_source(cls, setter_name, prop_name):
|
||||||
|
return """def %(setter)s(self, val):
|
||||||
|
old = self._prop_%(prop)s
|
||||||
|
new = type(self).create_value('%(prop)s', val, self)
|
||||||
|
self._prop_%(prop)s = new
|
||||||
|
if type(self).check_value_change(old, new): self._reset_property_notification('%(prop)s')
|
||||||
|
self.notify_property_value_change('%(prop)s', old, val)
|
||||||
|
return
|
||||||
|
""" % {'setter':setter_name, 'prop':prop_name}
|
||||||
|
|
||||||
|
pass #end of class
|
||||||
|
|
||||||
|
|
||||||
|
class ObservablePropertyMetaMT (ObservablePropertyMeta):
|
||||||
|
"""This class provides multithreading support for accesing
|
||||||
|
properties, through a locking mechanism. It is assumed a lock is
|
||||||
|
owned by the class that uses it. A Lock object called _prop_lock
|
||||||
|
is assumed to be a member of the using class. see for example class
|
||||||
|
ModelMT"""
|
||||||
|
def __init__(cls, name, bases, dict):
|
||||||
|
ObservablePropertyMeta.__init__(cls, name, bases, dict)
|
||||||
|
return
|
||||||
|
|
||||||
|
def get_setter_source(cls, setter_name, prop_name):
|
||||||
|
return """def %(setter)s(self, val):
|
||||||
|
old = self._prop_%(prop)s
|
||||||
|
new = type(self).create_value('%(prop)s', val, self)
|
||||||
|
self._prop_lock.acquire()
|
||||||
|
self._prop_%(prop)s = new
|
||||||
|
self._prop_lock.release()
|
||||||
|
if type(self).check_value_change(old, new): self._reset_property_notification('%(prop)s')
|
||||||
|
self.notify_property_value_change('%(prop)s', old, val)
|
||||||
|
return
|
||||||
|
""" % {'setter':setter_name, 'prop':prop_name}
|
||||||
|
|
||||||
|
pass #end of class
|
||||||
|
|
||||||
|
|
||||||
|
try:
|
||||||
|
from gobject import GObjectMeta
|
||||||
|
class ObservablePropertyGObjectMeta (ObservablePropertyMeta, GObjectMeta): pass
|
||||||
|
class ObservablePropertyGObjectMetaMT (ObservablePropertyMetaMT, GObjectMeta): pass
|
||||||
|
except:
|
||||||
|
class ObservablePropertyGObjectMeta (ObservablePropertyMeta): pass
|
||||||
|
class ObservablePropertyGObjectMetaMT (ObservablePropertyMetaMT): pass
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
65
src/gtkmvc/support/noconflict.py
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
# Author: Michele Simionato <michelesimionato@libero.it>
|
||||||
|
# Copyright (C) 2004 by Michele Simionato
|
||||||
|
# License: Python License (version not specified)
|
||||||
|
# Last Updated: 2nd of March 2007, 10:23 GMT
|
||||||
|
#
|
||||||
|
# Any serious user of metaclasses has been bitten at least once by
|
||||||
|
# the infamous metaclass/metatype conflict. This script contains a
|
||||||
|
# general recipe to solve the problem, as well as some theory and
|
||||||
|
# some examples.
|
||||||
|
|
||||||
|
|
||||||
|
import inspect, types, __builtin__
|
||||||
|
|
||||||
|
############## preliminary: two utility functions #####################
|
||||||
|
|
||||||
|
def skip_redundant(iterable, skipset=None):
|
||||||
|
"Redundant items are repeated items or items in the original skipset."
|
||||||
|
if skipset is None: skipset = set()
|
||||||
|
for item in iterable:
|
||||||
|
if item not in skipset:
|
||||||
|
skipset.add(item)
|
||||||
|
yield item
|
||||||
|
|
||||||
|
|
||||||
|
def remove_redundant(metaclasses):
|
||||||
|
skipset = set([types.ClassType])
|
||||||
|
for meta in metaclasses: # determines the metaclasses to be skipped
|
||||||
|
skipset.update(inspect.getmro(meta)[1:])
|
||||||
|
return tuple(skip_redundant(metaclasses, skipset))
|
||||||
|
|
||||||
|
##################################################################
|
||||||
|
## now the core of the module: two mutually recursive functions ##
|
||||||
|
##################################################################
|
||||||
|
|
||||||
|
memoized_metaclasses_map = {}
|
||||||
|
|
||||||
|
def get_noconflict_metaclass(bases, left_metas, right_metas):
|
||||||
|
"""Not intended to be used outside of this module, unless you know
|
||||||
|
what you are doing."""
|
||||||
|
# make tuple of needed metaclasses in specified priority order
|
||||||
|
metas = left_metas + tuple(map(type, bases)) + right_metas
|
||||||
|
needed_metas = remove_redundant(metas)
|
||||||
|
|
||||||
|
# return existing confict-solving meta, if any
|
||||||
|
if needed_metas in memoized_metaclasses_map:
|
||||||
|
return memoized_metaclasses_map[needed_metas]
|
||||||
|
# nope: compute, memoize and return needed conflict-solving meta
|
||||||
|
elif not needed_metas: # wee, a trivial case, happy us
|
||||||
|
meta = type
|
||||||
|
elif len(needed_metas) == 1: # another trivial case
|
||||||
|
meta = needed_metas[0]
|
||||||
|
# check for recursion, can happen i.e. for Zope ExtensionClasses
|
||||||
|
elif needed_metas == bases:
|
||||||
|
raise TypeError("Incompatible root metatypes", needed_metas)
|
||||||
|
else: # gotta work ...
|
||||||
|
metaname = '_' + ''.join([m.__name__ for m in needed_metas])
|
||||||
|
meta = classmaker()(metaname, needed_metas, {})
|
||||||
|
memoized_metaclasses_map[needed_metas] = meta
|
||||||
|
return meta
|
||||||
|
|
||||||
|
def classmaker(left_metas=(), right_metas=()):
|
||||||
|
def make_class(name, bases, adict):
|
||||||
|
metaclass = get_noconflict_metaclass(bases, left_metas, right_metas)
|
||||||
|
return metaclass(name, bases, adict)
|
||||||
|
return make_class
|
||||||
4
src/gtkmvc/support/test.py
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
class A (object):
|
||||||
|
a = 10
|
||||||
|
pass
|
||||||
|
|
||||||
39
src/gtkmvc/support/utils.py
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
# Author: Roberto Cavada <cavada@fbk.eu>
|
||||||
|
#
|
||||||
|
# Copyright (c) 2007 by Roberto Cavada
|
||||||
|
#
|
||||||
|
# pygtkmvc is free software; you can redistribute it and/or
|
||||||
|
# modify it under the terms of the GNU Lesser General Public
|
||||||
|
# License as published by the Free Software Foundation; either
|
||||||
|
# version 2 of the License, or (at your option) any later version.
|
||||||
|
#
|
||||||
|
# pygtkmvc 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
|
||||||
|
# Lesser General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU Lesser General Public
|
||||||
|
# License along with this library; if not, write to the Free Software
|
||||||
|
# Foundation, Inc., 51 Franklin Street, Fifth Floor,
|
||||||
|
# Boston, MA 02110, USA.
|
||||||
|
#
|
||||||
|
# For more information on pygtkmvc see <http://pygtkmvc.sourceforge.net>
|
||||||
|
# or email to the author Roberto Cavada <cavada@fbk.eu>.
|
||||||
|
# Please report bugs to <cavada@fbk.eu>.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def get_function_from_source(source):
|
||||||
|
"""Given source code of a function, a function object is
|
||||||
|
returned"""
|
||||||
|
|
||||||
|
import re
|
||||||
|
m = re.compile("def\s+(\w+)\s*\(.*\):").match(source)
|
||||||
|
if m is None: raise ValueError("Given source is not a valid function:\n"+
|
||||||
|
source)
|
||||||
|
name = m.group(1)
|
||||||
|
|
||||||
|
exec source
|
||||||
|
code = eval("%s.func_code" % name)
|
||||||
|
import new
|
||||||
|
return new.function(code, globals(), name)
|
||||||
162
src/gtkmvc/support/wrappers.py
Normal file
@@ -0,0 +1,162 @@
|
|||||||
|
# -------------------------------------------------------------------------
|
||||||
|
# Author: Roberto Cavada <cavada@fbk.eu>
|
||||||
|
#
|
||||||
|
# Copyright (C) 2006 by Roberto Cavada
|
||||||
|
#
|
||||||
|
# pygtkmvc is free software; you can redistribute it and/or
|
||||||
|
# modify it under the terms of the GNU Lesser General Public
|
||||||
|
# License as published by the Free Software Foundation; either
|
||||||
|
# version 2 of the License, or (at your option) any later version.
|
||||||
|
#
|
||||||
|
# pygtkmvc 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
|
||||||
|
# Lesser General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU Lesser General Public
|
||||||
|
# License along with this library; if not, write to the Free Software
|
||||||
|
# Foundation, Inc., 51 Franklin Street, Fifth Floor,
|
||||||
|
# Boston, MA 02110, USA.
|
||||||
|
#
|
||||||
|
# For more information on pygtkmvc see <http://pygtkmvc.sourceforge.net>
|
||||||
|
# or email to the author Roberto Cavada <cavada@fbk.eu>.
|
||||||
|
# Please report bugs to <cavada@fbk.eu>.
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
import new
|
||||||
|
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------
|
||||||
|
class ObsWrapperBase (object):
|
||||||
|
"""
|
||||||
|
This class is a base class wrapper for user-defined classes and
|
||||||
|
containers like lists and maps.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.__prop_name = None
|
||||||
|
self.__gtkmvc_model = None
|
||||||
|
return
|
||||||
|
|
||||||
|
def __set_model__(self, model, prop_name):
|
||||||
|
self.__prop_name = prop_name
|
||||||
|
self.__gtkmvc_model = model
|
||||||
|
return
|
||||||
|
|
||||||
|
def __get_prop_name__(self): return self.__prop_name
|
||||||
|
def __get_model__(self): return self.__gtkmvc_model
|
||||||
|
|
||||||
|
def _notify_method_before(self, instance, name, args, kwargs):
|
||||||
|
self.__get_model__().notify_method_before_change(self.__prop_name,
|
||||||
|
instance,
|
||||||
|
name, args, kwargs)
|
||||||
|
return
|
||||||
|
|
||||||
|
def _notify_method_after(self, instance, name, res_val, args, kwargs):
|
||||||
|
self.__get_model__().notify_method_after_change(self.__prop_name,
|
||||||
|
instance,
|
||||||
|
name, res_val, args, kwargs)
|
||||||
|
return
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------
|
||||||
|
class ObsWrapper (ObsWrapperBase):
|
||||||
|
"""
|
||||||
|
Base class for wrappers, like user-classes and sequences.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def __init__(self, obj, method_names):
|
||||||
|
ObsWrapperBase.__init__(self)
|
||||||
|
|
||||||
|
self._obj = obj
|
||||||
|
self.__doc__ = obj.__doc__
|
||||||
|
|
||||||
|
for name in method_names:
|
||||||
|
if hasattr(self._obj, name):
|
||||||
|
src = self.__get_wrapper_code(name)
|
||||||
|
exec src
|
||||||
|
|
||||||
|
code = eval("%s.func_code" % name)
|
||||||
|
func = new.function(code, globals())
|
||||||
|
meth = new.instancemethod(func, self, type(self).__name__)
|
||||||
|
setattr(self, name, meth)
|
||||||
|
pass
|
||||||
|
pass
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
|
def __get_wrapper_code(self, name):
|
||||||
|
return """def %(name)s(self, *args, **kwargs):
|
||||||
|
self._notify_method_before(self._obj, "%(name)s", args, kwargs)
|
||||||
|
res = self._obj.%(name)s(*args, **kwargs)
|
||||||
|
self._notify_method_after(self._obj, "%(name)s", res, args, kwargs)
|
||||||
|
return res""" % {'name' : name}
|
||||||
|
|
||||||
|
# For all fall backs
|
||||||
|
def __getattr__(self, name): return getattr(self._obj, name)
|
||||||
|
def __repr__(self): return self._obj.__repr__()
|
||||||
|
def __str__(self): return self._obj.__str__()
|
||||||
|
|
||||||
|
pass #end of class
|
||||||
|
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------
|
||||||
|
class ObsSeqWrapper (ObsWrapper):
|
||||||
|
def __init__(self, obj, method_names):
|
||||||
|
ObsWrapper.__init__(self, obj, method_names)
|
||||||
|
return
|
||||||
|
|
||||||
|
def __setitem__(self, key, val):
|
||||||
|
|
||||||
|
self._notify_method_before(self._obj, "__setitem__", (key,val), {})
|
||||||
|
res = self._obj.__setitem__(key, val)
|
||||||
|
self._notify_method_after(self._obj, "__setitem__", res, (key,val), {})
|
||||||
|
return res
|
||||||
|
|
||||||
|
def __delitem__(self, key):
|
||||||
|
self._notify_method_before(self._obj, "__delitem__", (key,), {})
|
||||||
|
res = self._obj.__delitem__(key)
|
||||||
|
self._notify_method_after(self._obj, "__delitem__", res, (key,), {})
|
||||||
|
return res
|
||||||
|
|
||||||
|
|
||||||
|
def __getitem__(self, key):
|
||||||
|
return self._obj.__getitem__(key)
|
||||||
|
|
||||||
|
pass #end of class
|
||||||
|
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------
|
||||||
|
class ObsMapWrapper (ObsSeqWrapper):
|
||||||
|
def __init__(self, m):
|
||||||
|
methods = ("clear", "pop", "popitem", "update",
|
||||||
|
"setdefault")
|
||||||
|
ObsSeqWrapper.__init__(self, m, methods)
|
||||||
|
return
|
||||||
|
pass #end of class
|
||||||
|
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------
|
||||||
|
class ObsListWrapper (ObsSeqWrapper):
|
||||||
|
def __init__(self, l):
|
||||||
|
methods = ("append", "extend", "insert",
|
||||||
|
"pop", "remove", "reverse", "sort")
|
||||||
|
ObsSeqWrapper.__init__(self, l, methods)
|
||||||
|
return
|
||||||
|
pass #end of class
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------
|
||||||
|
class ObsUserClassWrapper (ObsWrapper):
|
||||||
|
def __init__(self, user_class_instance, obs_method_names):
|
||||||
|
ObsWrapper.__init__(self, user_class_instance, obs_method_names)
|
||||||
|
return
|
||||||
|
pass #end of class
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
215
src/gtkmvc/view.py
Normal file
@@ -0,0 +1,215 @@
|
|||||||
|
# Author: Roberto Cavada <cavada@fbk.eu>
|
||||||
|
# Modified by: Guillaume Libersat <glibersat AT linux62.org>
|
||||||
|
#
|
||||||
|
# Copyright (c) 2005 by Roberto Cavada
|
||||||
|
# Copyright (c) 2007 by Guillaume Libersat
|
||||||
|
#
|
||||||
|
# pygtkmvc is free software; you can redistribute it and/or
|
||||||
|
# modify it under the terms of the GNU Lesser General Public
|
||||||
|
# License as published by the Free Software Foundation; either
|
||||||
|
# version 2 of the License, or (at your option) any later version.
|
||||||
|
#
|
||||||
|
# pygtkmvc 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
|
||||||
|
# Lesser General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU Lesser General Public
|
||||||
|
# License along with this library; if not, write to the Free Software
|
||||||
|
# Foundation, Inc., 51 Franklin Street, Fifth Floor,
|
||||||
|
# Boston, MA 02110, USA.
|
||||||
|
#
|
||||||
|
# For more information on pygtkmvc see <http://pygtkmvc.sourceforge.net>
|
||||||
|
# or email to the author Roberto Cavada <cavada@fbk.eu>.
|
||||||
|
# Please report bugs to <cavada@fbk.eu>.
|
||||||
|
|
||||||
|
|
||||||
|
import gtk.glade
|
||||||
|
from controller import Controller
|
||||||
|
import types
|
||||||
|
|
||||||
|
class View (object):
|
||||||
|
|
||||||
|
def __init__(self, controller, glade_filename=None,
|
||||||
|
glade_top_widget_name=None, parent_view=None, register=True):
|
||||||
|
"""If register is False you *must* call 'controller.register_view(self)'
|
||||||
|
from the derived class constructor (i.e. registration is delayed)
|
||||||
|
If filename is not given (or None) all following parameters must be
|
||||||
|
not given (or None). In that case widgets must be connected manually.
|
||||||
|
glade_top_widget_name can be either a string name or list of names."""
|
||||||
|
self.manualWidgets = {}
|
||||||
|
self.autoWidgets = None
|
||||||
|
|
||||||
|
self.xmlWidgets = []
|
||||||
|
|
||||||
|
# Sets a callback for custom widgets
|
||||||
|
gtk.glade.set_custom_handler(self._custom_widget_create)
|
||||||
|
|
||||||
|
if (( type(glade_top_widget_name) == types.StringType)
|
||||||
|
or (glade_top_widget_name is None) ):
|
||||||
|
wids = (glade_top_widget_name,)
|
||||||
|
else: wids = glade_top_widget_name # Already a list or tuple
|
||||||
|
|
||||||
|
# retrieves XML objects from glade
|
||||||
|
if (glade_filename is not None):
|
||||||
|
for i in range(0,len(wids)):
|
||||||
|
self.xmlWidgets.append(gtk.glade.XML(glade_filename, wids[i]))
|
||||||
|
pass
|
||||||
|
pass
|
||||||
|
|
||||||
|
# top widget list or singleton:
|
||||||
|
if (glade_top_widget_name is not None):
|
||||||
|
if len(wids) > 1:
|
||||||
|
self.m_topWidget = []
|
||||||
|
for i in range(0, len(wids)):
|
||||||
|
self.m_topWidget.append(self[wids[i]])
|
||||||
|
pass
|
||||||
|
else: self.m_topWidget = self[wids[0]]
|
||||||
|
else: self.m_topWidget = None
|
||||||
|
|
||||||
|
if (glade_filename is not None): self.__autoconnect_signals(controller)
|
||||||
|
if (register): controller.register_view(self)
|
||||||
|
if (not parent_view is None): self.set_parent_view(parent_view)
|
||||||
|
return
|
||||||
|
|
||||||
|
# Gives us the ability to do: view['widget_name'].action()
|
||||||
|
# Returns None if no widget name has been found.
|
||||||
|
def __getitem__(self, key):
|
||||||
|
wid = None
|
||||||
|
|
||||||
|
if self.autoWidgets:
|
||||||
|
if self.autoWidgets.has_key(key): wid = self.autoWidgets[key]
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
for xml in self.xmlWidgets:
|
||||||
|
wid = xml.get_widget(key)
|
||||||
|
if wid is not None: break
|
||||||
|
pass
|
||||||
|
pass
|
||||||
|
|
||||||
|
if wid is None:
|
||||||
|
# try with manually-added widgets:
|
||||||
|
if self.manualWidgets.has_key(key):
|
||||||
|
wid = self.manualWidgets[key]
|
||||||
|
pass
|
||||||
|
pass
|
||||||
|
return wid
|
||||||
|
|
||||||
|
# You can also add a single widget:
|
||||||
|
def __setitem__(self, key, wid):
|
||||||
|
self.manualWidgets[key] = wid
|
||||||
|
if (self.m_topWidget is None): self.m_topWidget = wid
|
||||||
|
return
|
||||||
|
|
||||||
|
def show(self):
|
||||||
|
ret = True
|
||||||
|
top = self.get_top_widget()
|
||||||
|
if type(top) in (types.ListType, types.TupleType):
|
||||||
|
for t in top:
|
||||||
|
if t is not None: ret = ret and t.show()
|
||||||
|
pass
|
||||||
|
elif (top is not None): ret = top.show_all()
|
||||||
|
else: ret = False
|
||||||
|
return ret
|
||||||
|
|
||||||
|
|
||||||
|
def hide(self):
|
||||||
|
top = self.get_top_widget()
|
||||||
|
if type(top) in (types.ListType, types.TupleType):
|
||||||
|
for t in top:
|
||||||
|
if t is not None: t.hide_all()
|
||||||
|
pass
|
||||||
|
elif top is not None: top.hide_all()
|
||||||
|
return
|
||||||
|
|
||||||
|
# Returns the top-level widget, or a list of top widgets
|
||||||
|
def get_top_widget(self):
|
||||||
|
return self.m_topWidget
|
||||||
|
|
||||||
|
|
||||||
|
# Set parent view:
|
||||||
|
def set_parent_view(self, parent_view):
|
||||||
|
top = self.get_top_widget()
|
||||||
|
if type(top) in (types.ListType, types.TupleType):
|
||||||
|
for t in top:
|
||||||
|
if t is not None:
|
||||||
|
t.set_transient_for(parent_view.get_top_widget())
|
||||||
|
pass
|
||||||
|
pass
|
||||||
|
elif (top is not None):
|
||||||
|
top.set_transient_for(parent_view.get_top_widget())
|
||||||
|
pass
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
|
# Set the transient for the view:
|
||||||
|
def set_transient(self, transient_view):
|
||||||
|
top = self.get_top_widget()
|
||||||
|
if type(top) in (types.ListType, types.TupleType):
|
||||||
|
for t in top:
|
||||||
|
if t is not None:
|
||||||
|
transient_view.get_top_widget().set_transient_for(t)
|
||||||
|
pass
|
||||||
|
pass
|
||||||
|
elif (top is not None):
|
||||||
|
transient_view.get_top_widget().set_transient_for(top)
|
||||||
|
pass
|
||||||
|
return
|
||||||
|
|
||||||
|
# Finds the right callback for custom widget creation and calls it
|
||||||
|
# Returns None if an undefined or invalid handler is found
|
||||||
|
def _custom_widget_create(self, glade, function_name, widget_name,
|
||||||
|
str1, str2, int1, int2):
|
||||||
|
# This code was kindly provided by Allan Douglas <zalguod at
|
||||||
|
# users.sourceforge.net>
|
||||||
|
if function_name is not None:
|
||||||
|
handler = getattr(self, function_name, None)
|
||||||
|
if handler is not None: return handler(str1, str2, int1, int2)
|
||||||
|
pass
|
||||||
|
return None
|
||||||
|
|
||||||
|
# implements the iteration protocol
|
||||||
|
def __iter__(self):
|
||||||
|
# pre-calculates the auto widgets if needed:
|
||||||
|
if self.autoWidgets is None:
|
||||||
|
self.autoWidgets = {}
|
||||||
|
|
||||||
|
for xml in self.xmlWidgets:
|
||||||
|
for wid in xml.get_widget_prefix(""):
|
||||||
|
wname = gtk.glade.get_widget_name(wid)
|
||||||
|
assert not self.autoWidgets.has_key(wname)
|
||||||
|
self.autoWidgets[wname] = wid
|
||||||
|
pass
|
||||||
|
pass
|
||||||
|
pass
|
||||||
|
|
||||||
|
self.__idx = 0
|
||||||
|
self.__max1 = len(self.autoWidgets)
|
||||||
|
self.__max2 = self.__max1 + len(self.manualWidgets)
|
||||||
|
return self
|
||||||
|
|
||||||
|
# implements the iteration protocol
|
||||||
|
def next(self):
|
||||||
|
if self.__idx >= self.__max2: raise StopIteration()
|
||||||
|
if self.__idx >= self.__max1: m = self.manualWidgets
|
||||||
|
else: m = self.autoWidgets
|
||||||
|
self.__idx += 1
|
||||||
|
return m.keys()[self.__idx-1]
|
||||||
|
|
||||||
|
|
||||||
|
# performs Controller's signals auto-connection:
|
||||||
|
def __autoconnect_signals(self, controller):
|
||||||
|
dic = {}
|
||||||
|
for name in dir(controller):
|
||||||
|
method = getattr(controller, name)
|
||||||
|
if (not callable(method)): continue
|
||||||
|
assert(not dic.has_key(name)) # not already connected!
|
||||||
|
dic[name] = method
|
||||||
|
pass
|
||||||
|
|
||||||
|
for xml in self.xmlWidgets: xml.signal_autoconnect(dic)
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
pass # end of class View
|
||||||
1781
src/lib/EXIF.py
Normal file
83
src/lib/EXIF_light_source_and_flash.patch
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
diff -au EXIF.py EXIF_mine.py
|
||||||
|
--- EXIF.py 2008-07-31 15:53:50.000000000 +0200
|
||||||
|
+++ EXIF_mine.py 2009-02-25 18:39:48.000000000 +0100
|
||||||
|
@@ -251,40 +251,54 @@
|
||||||
|
2: 'CenterWeightedAverage',
|
||||||
|
3: 'Spot',
|
||||||
|
4: 'MultiSpot',
|
||||||
|
- 5: 'Pattern'}),
|
||||||
|
+ 5: 'Pattern',
|
||||||
|
+ 6: 'Partial',
|
||||||
|
+ 255: 'other'}),
|
||||||
|
0x9208: ('LightSource',
|
||||||
|
{0: 'Unknown',
|
||||||
|
1: 'Daylight',
|
||||||
|
2: 'Fluorescent',
|
||||||
|
- 3: 'Tungsten',
|
||||||
|
- 9: 'Fine Weather',
|
||||||
|
- 10: 'Flash',
|
||||||
|
+ 3: 'Tungsten (incandescent light)',
|
||||||
|
+ 4: 'Flash',
|
||||||
|
+ 9: 'Fine weather',
|
||||||
|
+ 10: 'Cloudy weather',
|
||||||
|
11: 'Shade',
|
||||||
|
- 12: 'Daylight Fluorescent',
|
||||||
|
- 13: 'Day White Fluorescent',
|
||||||
|
- 14: 'Cool White Fluorescent',
|
||||||
|
- 15: 'White Fluorescent',
|
||||||
|
- 17: 'Standard Light A',
|
||||||
|
- 18: 'Standard Light B',
|
||||||
|
- 19: 'Standard Light C',
|
||||||
|
+ 12: 'Daylight fluorescent (D 5700 - 7100K)',
|
||||||
|
+ 13: 'Day white fluorescent (N 4600 - 5400K)',
|
||||||
|
+ 14: 'Cool white fluorescent (W 3900 - 4500K)',
|
||||||
|
+ 15: 'White fluorescent (WW 3200 - 3700K)',
|
||||||
|
+ 17: 'Standard light A',
|
||||||
|
+ 18: 'Standard light B',
|
||||||
|
+ 19: 'Standard light C',
|
||||||
|
20: 'D55',
|
||||||
|
21: 'D65',
|
||||||
|
22: 'D75',
|
||||||
|
- 255: 'Other'}),
|
||||||
|
+ 23: 'D50',
|
||||||
|
+ 24: 'ISO studio tungsten',
|
||||||
|
+ 255: 'other light source',}),
|
||||||
|
0x9209: ('Flash',
|
||||||
|
- {0: 'No',
|
||||||
|
- 1: 'Fired',
|
||||||
|
- 5: 'Fired (?)', # no return sensed
|
||||||
|
- 7: 'Fired (!)', # return sensed
|
||||||
|
- 9: 'Fill Fired',
|
||||||
|
- 13: 'Fill Fired (?)',
|
||||||
|
- 15: 'Fill Fired (!)',
|
||||||
|
- 16: 'Off',
|
||||||
|
- 24: 'Auto Off',
|
||||||
|
- 25: 'Auto Fired',
|
||||||
|
- 29: 'Auto Fired (?)',
|
||||||
|
- 31: 'Auto Fired (!)',
|
||||||
|
- 32: 'Not Available'}),
|
||||||
|
+ {0: 'Flash did not fire',
|
||||||
|
+ 1: 'Flash fired',
|
||||||
|
+ 5: 'Strobe return light not detected',
|
||||||
|
+ 7: 'Strobe return light detected',
|
||||||
|
+ 9: 'Flash fired, compulsory flash mode',
|
||||||
|
+ 13: 'Flash fired, compulsory flash mode, return light not detected',
|
||||||
|
+ 15: 'Flash fired, compulsory flash mode, return light detected',
|
||||||
|
+ 16: 'Flash did not fire, compulsory flash mode',
|
||||||
|
+ 24: 'Flash did not fire, auto mode',
|
||||||
|
+ 25: 'Flash fired, auto mode',
|
||||||
|
+ 29: 'Flash fired, auto mode, return light not detected',
|
||||||
|
+ 31: 'Flash fired, auto mode, return light detected',
|
||||||
|
+ 32: 'No flash function',
|
||||||
|
+ 65: 'Flash fired, red-eye reduction mode',
|
||||||
|
+ 69: 'Flash fired, red-eye reduction mode, return light not detected',
|
||||||
|
+ 71: 'Flash fired, red-eye reduction mode, return light detected',
|
||||||
|
+ 73: 'Flash fired, compulsory flash mode, red-eye reduction mode',
|
||||||
|
+ 77: 'Flash fired, compulsory flash mode, red-eye reduction mode, return light not detected',
|
||||||
|
+ 79: 'Flash fired, compulsory flash mode, red-eye reduction mode, return light detected',
|
||||||
|
+ 89: 'Flash fired, auto mode, red-eye reduction mode',
|
||||||
|
+ 93: 'Flash fired, auto mode, return light not detected, red-eye reduction mode',
|
||||||
|
+ 95: 'Flash fired, auto mode, return light detected, red-eye reduction mode'}),
|
||||||
|
0x920A: ('FocalLength', ),
|
||||||
|
0x9214: ('SubjectArea', ),
|
||||||
|
0x927C: ('MakerNote', ),
|
||||||
25
src/lib/__init__.py
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
# This Python file uses the following encoding: utf-8
|
||||||
|
#
|
||||||
|
# Author: Roman 'gryf' Dobosz gryf@elysium.pl
|
||||||
|
#
|
||||||
|
# Copyright (C) 2007 by Roman 'gryf' Dobosz
|
||||||
|
#
|
||||||
|
# This file is part of pyGTKtalog.
|
||||||
|
#
|
||||||
|
# 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
|
||||||
|
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
86
src/lib/device_helper.py
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
"""
|
||||||
|
Project: pyGTKtalog
|
||||||
|
Description: Simple functions for device management.
|
||||||
|
Type: lib
|
||||||
|
Author: Roman 'gryf' Dobosz, gryf73@gmail.com
|
||||||
|
Created: 2008-12-15
|
||||||
|
"""
|
||||||
|
import os
|
||||||
|
import locale
|
||||||
|
import gettext
|
||||||
|
|
||||||
|
from src.lib.globs import APPL_SHORT_NAME
|
||||||
|
|
||||||
|
locale.setlocale(locale.LC_ALL, '')
|
||||||
|
gettext.install(APPL_SHORT_NAME, 'locale', unicode=True)
|
||||||
|
|
||||||
|
def volname(mntp):
|
||||||
|
"""read volume name from cd/dvd"""
|
||||||
|
dev = mountpoint_to_dev(mntp)
|
||||||
|
label = None
|
||||||
|
if dev != None:
|
||||||
|
try:
|
||||||
|
disk = open(dev, "rb")
|
||||||
|
disk.seek(32808)
|
||||||
|
label = disk.read(32).strip()
|
||||||
|
disk.close()
|
||||||
|
except IOError:
|
||||||
|
return None
|
||||||
|
return label
|
||||||
|
|
||||||
|
def volmount(mntp):
|
||||||
|
"""
|
||||||
|
Mount device.
|
||||||
|
@param mountpoint
|
||||||
|
@returns tuple with bool status of mount, and string with error message
|
||||||
|
"""
|
||||||
|
_in, _out, _err = os.popen3("mount %s" % mntp)
|
||||||
|
inf = _err.readlines()
|
||||||
|
if len(inf) > 0:
|
||||||
|
return False, inf[0].strip()
|
||||||
|
else:
|
||||||
|
return True, ''
|
||||||
|
|
||||||
|
def volumount(mntp):
|
||||||
|
"""mount device, return 'ok' or error message"""
|
||||||
|
_in, _out, _err = os.popen3("umount %s" % mntp)
|
||||||
|
inf = _err.readlines()
|
||||||
|
if len(inf) > 0:
|
||||||
|
return inf[0].strip()
|
||||||
|
return 'ok'
|
||||||
|
|
||||||
|
def check_mount(dev):
|
||||||
|
"""Refresh the entries from fstab or mount."""
|
||||||
|
mounts = os.popen('mount')
|
||||||
|
for line in mounts.readlines():
|
||||||
|
parts = line.split()
|
||||||
|
device = parts
|
||||||
|
if device[0] == dev:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def mountpoint_to_dev(mntp):
|
||||||
|
"""guess device name by mountpoint from fstab"""
|
||||||
|
fstab = open("/etc/fstab")
|
||||||
|
device = None
|
||||||
|
for line in fstab.readlines():
|
||||||
|
output = line.split()
|
||||||
|
# lengtht of single valid fstab line is at least 5
|
||||||
|
if len(output) > 5 and output[1] == mntp and output[0][0] != '#':
|
||||||
|
device = output[0]
|
||||||
|
|
||||||
|
fstab.close()
|
||||||
|
return device
|
||||||
|
|
||||||
|
def eject_cd(eject_app, cdrom):
|
||||||
|
"""mount device, return 'ok' or error message"""
|
||||||
|
if len(eject_app) > 0:
|
||||||
|
_in, _out, _err = os.popen3("%s %s" % (eject_app, cdrom))
|
||||||
|
inf = _err.readlines()
|
||||||
|
|
||||||
|
if len(inf) > 0 and inf[0].strip() != '':
|
||||||
|
return inf[0].strip()
|
||||||
|
|
||||||
|
return 'ok'
|
||||||
|
return _("Eject program not specified")
|
||||||
|
|
||||||
41
src/lib/globs.py
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
# This Python file uses the following encoding: utf-8
|
||||||
|
#
|
||||||
|
# Author: Roman 'gryf' Dobosz gryf@elysium.pl
|
||||||
|
#
|
||||||
|
# Copyright (C) 2007 by Roman 'gryf' Dobosz
|
||||||
|
#
|
||||||
|
# This file is part of pyGTKtalog.
|
||||||
|
#
|
||||||
|
# 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
|
||||||
|
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
|
||||||
|
import os.path
|
||||||
|
import sys
|
||||||
|
|
||||||
|
if sys.argv[0]:
|
||||||
|
top_dir = os.path.dirname(os.path.abspath(sys.argv[0]))
|
||||||
|
else:
|
||||||
|
top_dir = "."
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------
|
||||||
|
TOPDIR = top_dir
|
||||||
|
RESOURCES_DIR = os.path.join(TOPDIR, "resources")
|
||||||
|
GLADE_DIR = os.path.join(RESOURCES_DIR, "glade")
|
||||||
|
STYLES_DIR = os.path.join(RESOURCES_DIR, "styles")
|
||||||
|
APPL_SHORT_NAME = "pygtktalog"
|
||||||
|
APPL_VERSION = (1, 0, 2)
|
||||||
|
# ----------------------------------------------------------------------
|
||||||
|
|
||||||
82
src/lib/gthumb.py
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
# This Python file uses the following encoding: utf-8
|
||||||
|
#
|
||||||
|
# Author: Roman 'gryf' Dobosz gryf@elysium.pl
|
||||||
|
#
|
||||||
|
# Copyright (C) 2007 by Roman 'gryf' Dobosz
|
||||||
|
#
|
||||||
|
# This file is part of pyGTKtalog.
|
||||||
|
#
|
||||||
|
# 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
|
||||||
|
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
from xml.dom import minidom
|
||||||
|
import gzip
|
||||||
|
import os
|
||||||
|
from datetime import date
|
||||||
|
|
||||||
|
class GthumbCommentParser(object):
|
||||||
|
"""Read and return comments created eith gThumb program"""
|
||||||
|
def __init__(self, image_path, image_filename):
|
||||||
|
self.path = image_path
|
||||||
|
self.filename = image_filename
|
||||||
|
|
||||||
|
def parse(self):
|
||||||
|
"""Return dictionary with apropriate fields, or None if no comment
|
||||||
|
available"""
|
||||||
|
try:
|
||||||
|
gzf = gzip.open(os.path.join(self.path, '.comments',
|
||||||
|
self.filename + '.xml'))
|
||||||
|
except:
|
||||||
|
return None
|
||||||
|
|
||||||
|
try:
|
||||||
|
xml = gzf.read()
|
||||||
|
gzf.close()
|
||||||
|
except:
|
||||||
|
gzf.close()
|
||||||
|
return None
|
||||||
|
|
||||||
|
if not xml:
|
||||||
|
return None
|
||||||
|
|
||||||
|
retval = {}
|
||||||
|
doc = minidom.parseString(xml)
|
||||||
|
|
||||||
|
try:
|
||||||
|
retval['note'] = doc.getElementsByTagName('Note').item(0)
|
||||||
|
retval['note'] = retval['note'].childNodes.item(0).data
|
||||||
|
except: retval['note'] = None
|
||||||
|
|
||||||
|
try:
|
||||||
|
retval['place'] = doc.getElementsByTagName('Place').item(0)
|
||||||
|
retval['place'] = retval['place'].childNodes.item(0).data
|
||||||
|
except: retval['place'] = None
|
||||||
|
|
||||||
|
try:
|
||||||
|
d = doc.getElementsByTagName('Time').item(0).childNodes
|
||||||
|
d = d.item(0).data
|
||||||
|
if int(d) > 0: retval['date'] = date.fromtimestamp(int(d))
|
||||||
|
else: retval['date'] = None
|
||||||
|
except: retval['date'] = None
|
||||||
|
|
||||||
|
try:
|
||||||
|
retval['keywords'] = doc.getElementsByTagName('Keywords').item(0)
|
||||||
|
retval['keywords'] = retval['keywords'].childNodes.item(0)
|
||||||
|
retval['keywords'] = retval['keywords'].data.split(',')
|
||||||
|
except: pass
|
||||||
|
|
||||||
|
if len(retval) > 0: return retval
|
||||||
|
else: return None
|
||||||
|
|
||||||
77
src/lib/img.py
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
# This Python file uses the following encoding: utf-8
|
||||||
|
#
|
||||||
|
# Author: Roman 'gryf' Dobosz gryf@elysium.pl
|
||||||
|
#
|
||||||
|
# Copyright (C) 2007 by Roman 'gryf' Dobosz
|
||||||
|
#
|
||||||
|
# This file is part of pyGTKtalog.
|
||||||
|
#
|
||||||
|
# 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
|
||||||
|
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
|
||||||
|
from shutil import copy
|
||||||
|
from os import path
|
||||||
|
from hashlib import sha512
|
||||||
|
|
||||||
|
import Image
|
||||||
|
|
||||||
|
class Img(object):
|
||||||
|
|
||||||
|
def __init__(self, filename, base=''):
|
||||||
|
self.root = 'images'
|
||||||
|
self.x = 160
|
||||||
|
self.y = 160
|
||||||
|
self.filename = filename
|
||||||
|
self.base = base
|
||||||
|
f = open(filename, "r")
|
||||||
|
self.sha512 = sha512(f.read()).hexdigest()
|
||||||
|
f.close()
|
||||||
|
|
||||||
|
def save(self):
|
||||||
|
"""Save image and asociated thumbnail into specific directory structure
|
||||||
|
returns filename for image"""
|
||||||
|
|
||||||
|
image_filename = path.join(self.base, self.sha512)
|
||||||
|
thumbnail = path.join(self.base, self.sha512 + "_t")
|
||||||
|
|
||||||
|
# check wheter image already exists
|
||||||
|
if path.exists(image_filename) and path.exists(thumbnail):
|
||||||
|
if __debug__:
|
||||||
|
print "image", self.filename, "with hash",
|
||||||
|
print self.sha512, "already exist"
|
||||||
|
return self.sha512
|
||||||
|
|
||||||
|
if not path.exists(thumbnail):
|
||||||
|
im = self.__scale_image()
|
||||||
|
im.save(thumbnail, "JPEG")
|
||||||
|
|
||||||
|
# copy image
|
||||||
|
if not path.exists(image_filename):
|
||||||
|
copy(self.filename, image_filename)
|
||||||
|
|
||||||
|
return self.sha512
|
||||||
|
|
||||||
|
# private class functions
|
||||||
|
def __scale_image(self):
|
||||||
|
"""create thumbnail. returns image object or None"""
|
||||||
|
try:
|
||||||
|
im = Image.open(self.filename).convert('RGB')
|
||||||
|
except:
|
||||||
|
return None
|
||||||
|
x, y = im.size
|
||||||
|
if x > self.x or y > self.y:
|
||||||
|
im.thumbnail((self.x, self.y), Image.ANTIALIAS)
|
||||||
|
return im
|
||||||
22
src/lib/misc.py
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
"""
|
||||||
|
Project: pyGTKtalog
|
||||||
|
Description: Misc functions used more than once in src
|
||||||
|
Type: lib
|
||||||
|
Author: Roman 'gryf' Dobosz, gryf73@gmail.com
|
||||||
|
Created: 2008-12-15
|
||||||
|
"""
|
||||||
|
|
||||||
|
def float_to_string(float_length):
|
||||||
|
"""
|
||||||
|
Parse float digit into time string
|
||||||
|
Arguments:
|
||||||
|
@string - width of generated image. If actual image width
|
||||||
|
exceeds this number scale is performed.
|
||||||
|
Returns HH:MM:SS formatted string
|
||||||
|
"""
|
||||||
|
hour = int(float_length / 3600);
|
||||||
|
float_length -= hour*3600
|
||||||
|
minutes = int(float_length / 60)
|
||||||
|
float_length -= minutes * 60
|
||||||
|
sec = int(float_length)
|
||||||
|
return "%02d:%02d:%02d" % (hour, minutes, sec)
|
||||||
2397
src/lib/no_thumb.py
Normal file
117
src/lib/parse_exif.py
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
import re
|
||||||
|
import EXIF
|
||||||
|
class ParseExif(object):
|
||||||
|
|
||||||
|
def __init__(self, exif_dict=None, exif_file=None):
|
||||||
|
self.camera = None
|
||||||
|
self.date = None
|
||||||
|
self.aperture = None
|
||||||
|
self.exposure_program = None
|
||||||
|
self.exposure_bias = None
|
||||||
|
self.iso = None
|
||||||
|
self.focal_length = None
|
||||||
|
self.subject_distance = None
|
||||||
|
self.metering_mode = None
|
||||||
|
self.flash = None
|
||||||
|
self.light_source = None
|
||||||
|
self.resolution = None
|
||||||
|
self.orientation = None
|
||||||
|
self.exif_dict = exif_dict
|
||||||
|
if not self.exif_dict:
|
||||||
|
try:
|
||||||
|
f = open(exif_file, 'rb')
|
||||||
|
e = EXIF.process_file(f)
|
||||||
|
if len(e.keys()) >0:
|
||||||
|
self.exif_dict = e
|
||||||
|
f.close()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def parse(self):
|
||||||
|
try:
|
||||||
|
self.camera = "%s" % self.exif_dict['Image Make']
|
||||||
|
self.camera = self.camera.strip()
|
||||||
|
except: pass
|
||||||
|
try:
|
||||||
|
model = "%s" % self.exif_dict['Image Model']
|
||||||
|
self.camera += ", " + model.strip()
|
||||||
|
except: pass
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.date = "%s" % self.exif_dict['EXIF DateTimeOriginal']
|
||||||
|
p = re.compile('[\d,:]+')
|
||||||
|
if not p.match(self.date):
|
||||||
|
self.date = None
|
||||||
|
except: pass
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.aperture = "%s" % self.exif_dict['EXIF FNumber']
|
||||||
|
if len(self.aperture.split("/")) == 2:
|
||||||
|
self.aperture += '.'
|
||||||
|
self.aperture = "%.1f" % eval(self.aperture)
|
||||||
|
self.aperture = "f/%.1f" % float(self.aperture)
|
||||||
|
self.aperture = self.aperture.replace('.',',')
|
||||||
|
except: pass
|
||||||
|
|
||||||
|
try: self.exposure_program = "%s" % \
|
||||||
|
self.exif_dict['EXIF ExposureProgram']
|
||||||
|
except: pass
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.exposure_bias = "%s" % \
|
||||||
|
self.exif_dict['EXIF ExposureBiasValue']
|
||||||
|
if len(self.exposure_bias.split("/")) == 2:
|
||||||
|
self.exposure_bias += '.'
|
||||||
|
self.exposure_bias = "%.1f" % eval(self.exposure_bias)
|
||||||
|
self.exposure_bias = "%.1f" % float(self.exposure_bias)
|
||||||
|
self.exposure_bias = self.exposure_bias.replace('.',',')
|
||||||
|
except: pass
|
||||||
|
|
||||||
|
try: self.iso = "%s" % self.exif_dict['EXIF ISOSpeedRatings']
|
||||||
|
except: pass
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.focal_length = "%s" % self.exif_dict['EXIF FocalLength']
|
||||||
|
if len(self.focal_length.split("/")) == 2:
|
||||||
|
self.focal_length += '.'
|
||||||
|
self.focal_length = "%.2f" % eval(self.focal_length)
|
||||||
|
self.focal_length = "%.2f mm" % float(self.focal_length)
|
||||||
|
self.focal_length = self.focal_length.replace('.',',')
|
||||||
|
except: pass
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.subject_distance = "%s" % \
|
||||||
|
self.exif_dict['EXIF SubjectDistance']
|
||||||
|
if len(self.subject_distance.split("/")) == 2:
|
||||||
|
self.subject_distance += '.'
|
||||||
|
self.subject_distance = "%.3f" % eval(self.subject_distance)
|
||||||
|
self.subject_distance = "%.3f m" % float(self.subject_distance)
|
||||||
|
self.subject_distance = self.subject_distance.replace('.',',')
|
||||||
|
except: pass
|
||||||
|
|
||||||
|
try: self.metering_mode = "%s" % self.exif_dict['EXIF MeteringMode']
|
||||||
|
except: pass
|
||||||
|
|
||||||
|
try: self.flash = "%s" % self.exif_dict['EXIF Flash']
|
||||||
|
except: pass
|
||||||
|
|
||||||
|
try: self.light_source = "%s" % self.exif_dict['EXIF LightSource']
|
||||||
|
except: pass
|
||||||
|
|
||||||
|
try: self.resolution = "%s" % self.exif_dict['Image XResolution']
|
||||||
|
except: pass
|
||||||
|
try: self.resolution = self.resolution + " x %s" % \
|
||||||
|
self.exif_dict['Image YResolution']
|
||||||
|
except: pass
|
||||||
|
try: self.resolution = self.resolution + " (%s)" % \
|
||||||
|
self.exif_dict['Image ResolutionUnit']
|
||||||
|
except: pass
|
||||||
|
|
||||||
|
try: self.orientation = "%s" % self.exif_dict['Image Orientation']
|
||||||
|
except: pass
|
||||||
|
|
||||||
|
return (self.camera, self.date, self.aperture, self.exposure_program,
|
||||||
|
self.exposure_bias, self.iso, self.focal_length,
|
||||||
|
self.subject_distance, self.metering_mode, self.flash,
|
||||||
|
self.light_source, self.resolution, self.orientation)
|
||||||
|
|
||||||
116
src/lib/thumbnail.py
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
# This Python file uses the following encoding: utf-8
|
||||||
|
#
|
||||||
|
# Author: Roman 'gryf' Dobosz gryf@elysium.pl
|
||||||
|
#
|
||||||
|
# Copyright (C) 2007 by Roman 'gryf' Dobosz
|
||||||
|
#
|
||||||
|
# This file is part of pyGTKtalog.
|
||||||
|
#
|
||||||
|
# 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
|
||||||
|
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
|
||||||
|
from tempfile import mkstemp
|
||||||
|
from hashlib import sha512
|
||||||
|
from shutil import move
|
||||||
|
from os import path
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from lib import EXIF
|
||||||
|
import Image
|
||||||
|
|
||||||
|
class Thumbnail(object):
|
||||||
|
"""Class for generate/extract thumbnail from image file"""
|
||||||
|
|
||||||
|
def __init__(self, filename=None, base=''):
|
||||||
|
self.thumb_x = 160
|
||||||
|
self.thumb_y = 160
|
||||||
|
self.filename = filename
|
||||||
|
self.base = base
|
||||||
|
self.sha512 = sha512(open(filename).read()).hexdigest()
|
||||||
|
self.thumbnail_path = path.join(self.base, self.sha512 + "_t")
|
||||||
|
|
||||||
|
def save(self):
|
||||||
|
"""Save thumbnail into specific directory structure
|
||||||
|
return filename base and exif object or None"""
|
||||||
|
exif = {}
|
||||||
|
orientations = {2: Image.FLIP_LEFT_RIGHT, # Mirrored horizontal
|
||||||
|
3: Image.ROTATE_180, # Rotated 180
|
||||||
|
4: Image.FLIP_TOP_BOTTOM, # Mirrored vertical
|
||||||
|
5: Image.ROTATE_90, # Mirrored horizontal then
|
||||||
|
# rotated 90 CCW
|
||||||
|
6: Image.ROTATE_270, # Rotated 90 CW
|
||||||
|
7: Image.ROTATE_270, # Mirrored horizontal then
|
||||||
|
# rotated 90 CW
|
||||||
|
8: Image.ROTATE_90} # Rotated 90 CCW
|
||||||
|
flips = {7: Image.FLIP_LEFT_RIGHT, 5: Image.FLIP_LEFT_RIGHT}
|
||||||
|
|
||||||
|
image_file = open(self.filename, 'rb')
|
||||||
|
try:
|
||||||
|
exif = EXIF.process_file(image_file)
|
||||||
|
except:
|
||||||
|
if __debug__:
|
||||||
|
print "exception", sys.exc_info()[0], "raised with file:"
|
||||||
|
print self.filename
|
||||||
|
finally:
|
||||||
|
image_file.close()
|
||||||
|
|
||||||
|
if path.exists(self.thumbnail_path):
|
||||||
|
if __debug__:
|
||||||
|
print "file", self.filename, "with hash", self.sha512, "exists"
|
||||||
|
return self.sha512, exif
|
||||||
|
|
||||||
|
if 'JPEGThumbnail' in exif:
|
||||||
|
if __debug__:
|
||||||
|
print self.filename, "exif thumb"
|
||||||
|
exif_thumbnail = exif['JPEGThumbnail']
|
||||||
|
thumb_file = open(self.thumbnail_path, 'wb')
|
||||||
|
thumb_file.write(exif_thumbnail)
|
||||||
|
thumb_file.close()
|
||||||
|
|
||||||
|
if 'Image Orientation' in exif:
|
||||||
|
orient = exif['Image Orientation'].values[0]
|
||||||
|
if orient > 1 and orient in orientations:
|
||||||
|
temp_image_path = mkstemp()[1]
|
||||||
|
|
||||||
|
thumb_image = Image.open(self.thumbnail_path)
|
||||||
|
tmp_thumb_img = thumb_image.transpose(orientations[orient])
|
||||||
|
|
||||||
|
if orient in flips:
|
||||||
|
tmp_thumb_img = tmp_thumb_img.transpose(flips[orient])
|
||||||
|
|
||||||
|
if tmp_thumb_img:
|
||||||
|
tmp_thumb_img.save(temp_image_path, 'JPEG')
|
||||||
|
move(temp_image_path, self.thumbnail_path)
|
||||||
|
return self.sha512, exif
|
||||||
|
else:
|
||||||
|
if __debug__:
|
||||||
|
print self.filename, "no exif thumb"
|
||||||
|
thumb = self.__scale_image()
|
||||||
|
if thumb:
|
||||||
|
thumb.save(self.thumbnail_path, "JPEG")
|
||||||
|
return self.sha512, exif
|
||||||
|
return None, exif
|
||||||
|
|
||||||
|
def __scale_image(self):
|
||||||
|
"""create thumbnail. returns image object or None"""
|
||||||
|
try:
|
||||||
|
image_thumb = Image.open(self.filename).convert('RGB')
|
||||||
|
except:
|
||||||
|
return None
|
||||||
|
it_x, it_y = image_thumb.size
|
||||||
|
if it_x > self.thumb_x or it_y > self.thumb_y:
|
||||||
|
image_thumb.thumbnail((self.thumb_x, self.thumb_y), Image.ANTIALIAS)
|
||||||
|
return image_thumb
|
||||||
170
src/lib/video.py
Normal file
@@ -0,0 +1,170 @@
|
|||||||
|
"""
|
||||||
|
Project: pyGTKtalog
|
||||||
|
Description: Gather video file information, make "screenshot" with content
|
||||||
|
of the movie file. Uses external tools like mplayer and
|
||||||
|
ImageMagick tools (montage, convert).
|
||||||
|
Type: lib
|
||||||
|
Author: Roman 'gryf' Dobosz, gryf73@gmail.com
|
||||||
|
Created: 2009-04-04
|
||||||
|
"""
|
||||||
|
import os
|
||||||
|
import shutil
|
||||||
|
from tempfile import mkdtemp, mkstemp
|
||||||
|
import math
|
||||||
|
|
||||||
|
from misc import float_to_string
|
||||||
|
|
||||||
|
class Video(object):
|
||||||
|
"""Class for retrive midentify script output and put it in dict.
|
||||||
|
Usually there is no need for such a detailed movie/clip information.
|
||||||
|
Midentify script belongs to mplayer package.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, filename):
|
||||||
|
"""Init class instance. Filename of a video file is required."""
|
||||||
|
self.filename = filename
|
||||||
|
self.tags = {}
|
||||||
|
|
||||||
|
output = os.popen('midentify "%s"' % self.filename).readlines()
|
||||||
|
|
||||||
|
attrs = {'ID_VIDEO_WIDTH': ['width', int],
|
||||||
|
'ID_VIDEO_HEIGHT': ['height', int],
|
||||||
|
'ID_LENGTH': ['length', lambda x: int(x.split(".")[0])],
|
||||||
|
# length is in seconds
|
||||||
|
'ID_DEMUXER': ['container', str],
|
||||||
|
'ID_VIDEO_FORMAT': ['video_format', str],
|
||||||
|
'ID_VIDEO_CODEC': ['video_codec', str],
|
||||||
|
'ID_AUDIO_CODEC': ['audio_codec', str],
|
||||||
|
'ID_AUDIO_FORMAT': ['audio_format', str],
|
||||||
|
'ID_AUDIO_NCH': ['audio_no_channels', int],}
|
||||||
|
|
||||||
|
for line in output:
|
||||||
|
line = line.strip()
|
||||||
|
for attr in attrs:
|
||||||
|
if attr in line:
|
||||||
|
self.tags[attrs[attr][0]] = \
|
||||||
|
attrs[attr][1](line.replace("%s=" % attr, ""))
|
||||||
|
|
||||||
|
if 'length' in self.tags:
|
||||||
|
if self.tags['length'] > 0:
|
||||||
|
hours = self.tags['length'] / 3600
|
||||||
|
seconds = self.tags['length'] - hours * 3600
|
||||||
|
minutes = seconds / 60
|
||||||
|
seconds -= minutes * 60
|
||||||
|
length_str = "%02d:%02d:%02d" % (hours, minutes, seconds)
|
||||||
|
self.tags['duration'] = length_str
|
||||||
|
|
||||||
|
def capture(self, out_width=1024):
|
||||||
|
"""
|
||||||
|
Extract images for given video filename and montage it into one, big
|
||||||
|
picture, similar to output from Windows Media Player thing, but without
|
||||||
|
captions and time (who need it anyway?).
|
||||||
|
Arguments:
|
||||||
|
@out_width - width of generated image. If actual image width
|
||||||
|
exceeds this number scale is performed.
|
||||||
|
Returns: image filename or None
|
||||||
|
|
||||||
|
NOTE: You should remove returned file manually, or move it in some
|
||||||
|
other place, otherwise it stays in filesystem.
|
||||||
|
"""
|
||||||
|
|
||||||
|
if not (self.tags.has_key('length') or self.tags.has_key('width')):
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Calculate number of pictures. Base is equivalent 72 pictures for
|
||||||
|
# 1:30:00 movie length
|
||||||
|
scale = int(10 * math.log(self.tags['length'], math.e) - 11)
|
||||||
|
no_pictures = self.tags['length'] / scale
|
||||||
|
if no_pictures > 8:
|
||||||
|
# for really short movies
|
||||||
|
no_pictures = (no_pictures / 8 ) * 8 # only multiple of 8, please.
|
||||||
|
|
||||||
|
if not no_pictures:
|
||||||
|
# movie too short or length is 0
|
||||||
|
return None
|
||||||
|
|
||||||
|
if no_pictures < 4:
|
||||||
|
no_pictures = 4
|
||||||
|
|
||||||
|
tempdir = mkdtemp()
|
||||||
|
file_desc, image_fn = mkstemp()
|
||||||
|
os.close(file_desc)
|
||||||
|
self.__make_captures(tempdir, no_pictures)
|
||||||
|
self.__make_montage(tempdir, image_fn, no_pictures, out_width)
|
||||||
|
|
||||||
|
shutil.rmtree(tempdir)
|
||||||
|
return image_fn
|
||||||
|
|
||||||
|
|
||||||
|
def __make_captures(self, directory, no_pictures):
|
||||||
|
"""
|
||||||
|
Make screens with mplayer into given directory
|
||||||
|
Arguments:
|
||||||
|
@directory - full output directory name
|
||||||
|
@no_pictures - number of pictures to take
|
||||||
|
"""
|
||||||
|
step = float(self.tags['length']/(no_pictures + 1))
|
||||||
|
current_time = 0
|
||||||
|
for dummy in range(1, no_pictures + 1):
|
||||||
|
current_time += step
|
||||||
|
time = float_to_string(current_time)
|
||||||
|
cmd = "mplayer \"%s\" -ao null -brightness 0 -hue 0 " \
|
||||||
|
"-saturation 0 -contrast 0 -vf-clr -vo jpeg:outdir=\"%s\" -ss %s" \
|
||||||
|
" -frames 1 2>/dev/null"
|
||||||
|
os.popen(cmd % (self.filename, directory, time)).readlines()
|
||||||
|
|
||||||
|
shutil.move(os.path.join(directory, "00000001.jpg"),
|
||||||
|
os.path.join(directory, "picture_%s.jpg" % time))
|
||||||
|
|
||||||
|
def __make_montage(self, directory, image_fn, no_pictures, out_width):
|
||||||
|
"""
|
||||||
|
Generate one big image from screnshots and optionally resize it.
|
||||||
|
Arguments:
|
||||||
|
@directory - source directory containing images
|
||||||
|
@image_fn - destination final image
|
||||||
|
@no_pictures - number of pictures
|
||||||
|
@out_width - width of final image to be scaled to.
|
||||||
|
"""
|
||||||
|
scale = False
|
||||||
|
row_length = 4
|
||||||
|
if no_pictures < 8:
|
||||||
|
row_length = 2
|
||||||
|
|
||||||
|
if (self.tags['width'] * row_length) > out_width:
|
||||||
|
scale = True
|
||||||
|
else:
|
||||||
|
for i in [8, 6, 5]:
|
||||||
|
if (no_pictures % i) == 0 and \
|
||||||
|
(i * self.tags['width']) <= out_width:
|
||||||
|
row_length = i
|
||||||
|
break
|
||||||
|
|
||||||
|
tile = "%dx%d" % (row_length, no_pictures / row_length)
|
||||||
|
|
||||||
|
_curdir = os.path.abspath(os.path.curdir)
|
||||||
|
os.chdir(directory)
|
||||||
|
|
||||||
|
# composite pictures
|
||||||
|
# readlines trick will make to wait for process end
|
||||||
|
cmd = "montage -tile %s -geometry +2+2 picture_*.jpg montage.jpg"
|
||||||
|
os.popen(cmd % tile).readlines()
|
||||||
|
|
||||||
|
# scale it to minimum 'modern' width: 1024
|
||||||
|
if scale:
|
||||||
|
cmd = "convert -scale %s montage.jpg montage_scaled.jpg"
|
||||||
|
os.popen(cmd % out_width).readlines()
|
||||||
|
shutil.move(os.path.join(directory, 'montage_scaled.jpg'),
|
||||||
|
image_fn)
|
||||||
|
else:
|
||||||
|
shutil.move(os.path.join(directory, 'montage.jpg'),
|
||||||
|
image_fn)
|
||||||
|
|
||||||
|
os.chdir(_curdir)
|
||||||
|
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
str_out = ''
|
||||||
|
for key in self.tags:
|
||||||
|
str_out += "%20s: %s\n" % (key, self.tags[key])
|
||||||
|
return str_out
|
||||||
|
|
||||||
25
src/models/__init__.py
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
# This Python file uses the following encoding: utf-8
|
||||||
|
#
|
||||||
|
# Author: Roman 'gryf' Dobosz gryf@elysium.pl
|
||||||
|
#
|
||||||
|
# Copyright (C) 2007 by Roman 'gryf' Dobosz
|
||||||
|
#
|
||||||
|
# This file is part of pyGTKtalog.
|
||||||
|
#
|
||||||
|
# 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
|
||||||
|
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
340
src/models/m_config.py
Normal file
@@ -0,0 +1,340 @@
|
|||||||
|
# This Python file uses the following encoding: utf-8
|
||||||
|
#
|
||||||
|
# Author: Roman 'gryf' Dobosz gryf@elysium.pl
|
||||||
|
#
|
||||||
|
# Copyright (C) 2007 by Roman 'gryf' Dobosz
|
||||||
|
#
|
||||||
|
# This file is part of pyGTKtalog.
|
||||||
|
#
|
||||||
|
# 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
|
||||||
|
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
|
||||||
|
from gtkmvc import Model
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
import gtk
|
||||||
|
import gobject
|
||||||
|
|
||||||
|
from ConfigParser import ConfigParser
|
||||||
|
|
||||||
|
class Ini(object):
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.ini = []
|
||||||
|
|
||||||
|
def add_section(self, section):
|
||||||
|
self.ini.append("[%s]" % section)
|
||||||
|
|
||||||
|
def add_key(self, key, value):
|
||||||
|
self.ini.append("%s=%s" % (key, value))
|
||||||
|
|
||||||
|
def add_comment(self, comment):
|
||||||
|
self.ini.append(";%s" % comment)
|
||||||
|
|
||||||
|
def add_verb(self, verb):
|
||||||
|
self.ini.append(verb)
|
||||||
|
|
||||||
|
def show(self):
|
||||||
|
return "\n".join(self.ini)
|
||||||
|
|
||||||
|
class ConfigModel(Model):
|
||||||
|
ini = Ini()
|
||||||
|
|
||||||
|
__properties__ = {}
|
||||||
|
|
||||||
|
filetype_list = ['Images', 'Movies']
|
||||||
|
|
||||||
|
confd = {
|
||||||
|
'savewin': True,
|
||||||
|
'savepan': True,
|
||||||
|
'wx': 800,
|
||||||
|
'wy': 600,
|
||||||
|
'h': 200,
|
||||||
|
'v': 300,
|
||||||
|
'eject': True,
|
||||||
|
'compress': True,
|
||||||
|
|
||||||
|
'exportxls': False,
|
||||||
|
|
||||||
|
'confirmquit': True,
|
||||||
|
'confirmabandon': True,
|
||||||
|
'mntwarn': True,
|
||||||
|
'delwarn': True,
|
||||||
|
|
||||||
|
'cd': '/mnt/cdrom',
|
||||||
|
'ejectapp': 'eject -r',
|
||||||
|
|
||||||
|
'imgview': False,
|
||||||
|
'imgprog': 'gqview',
|
||||||
|
|
||||||
|
'retrive': False,
|
||||||
|
|
||||||
|
'thumbs': True,
|
||||||
|
'exif': True,
|
||||||
|
'gthumb': False,
|
||||||
|
|
||||||
|
'extensions': {'bmp':'identify %s',
|
||||||
|
'gif':'identify "%s"',
|
||||||
|
'jpg':'identify "%s"',
|
||||||
|
'jpeg':'identify "%s"',
|
||||||
|
'png':'identify "%s"',
|
||||||
|
'avi':'midentify "%s"',
|
||||||
|
'mkv':'midentify "%s"',
|
||||||
|
'mpg':'midentify "%s"',
|
||||||
|
'mpeg':'midentify "%s"',
|
||||||
|
'wmv':'midentify "%s"',
|
||||||
|
},
|
||||||
|
|
||||||
|
'showtoolbar':True,
|
||||||
|
'showstatusbar':True,
|
||||||
|
}
|
||||||
|
|
||||||
|
dictconf = {
|
||||||
|
"save main window size": "savewin",
|
||||||
|
"save panes size": "savepan",
|
||||||
|
"main window width": "wx",
|
||||||
|
"main window height": "wy",
|
||||||
|
"horizontal panes": "h",
|
||||||
|
"vertical panes":"v",
|
||||||
|
"export xls":"exportxls",
|
||||||
|
"cd drive":"cd",
|
||||||
|
"eject command":"ejectapp",
|
||||||
|
"eject":"eject",
|
||||||
|
"image support":"thumbs",
|
||||||
|
'confirm quit':'confirmquit',
|
||||||
|
'warn mount/umount errors':'mntwarn',
|
||||||
|
'warn on delete':'delwarn',
|
||||||
|
'confirm abandon current catalog':'confirmabandon',
|
||||||
|
'show toolbar':'showtoolbar',
|
||||||
|
'show statusbar and progress bar':'showstatusbar',
|
||||||
|
'compress catalog':'compress',
|
||||||
|
'retrive extra informatin':'retrive',
|
||||||
|
'scan exif data':'exif',
|
||||||
|
'include gthumb image description':'gthumb',
|
||||||
|
'use external image viewer':'imgview',
|
||||||
|
'external image viewer program':'imgprog',
|
||||||
|
}
|
||||||
|
|
||||||
|
dbool = (
|
||||||
|
'exportxls',
|
||||||
|
'thumbs',
|
||||||
|
'savewin',
|
||||||
|
'savepan',
|
||||||
|
'eject',
|
||||||
|
'gthumb',
|
||||||
|
'exif',
|
||||||
|
'confirmquit',
|
||||||
|
'mntwarn',
|
||||||
|
'delwarn',
|
||||||
|
'confirmabandon',
|
||||||
|
'showtoolbar',
|
||||||
|
'showstatusbar',
|
||||||
|
'delwarn',
|
||||||
|
'compress',
|
||||||
|
'retrive',
|
||||||
|
'imgview',
|
||||||
|
)
|
||||||
|
|
||||||
|
recent = []
|
||||||
|
search_history = []
|
||||||
|
RECENT_MAX = 10
|
||||||
|
HISTORY_MAX = 20
|
||||||
|
|
||||||
|
dstring = ('cd','ejectapp','imgprog')
|
||||||
|
|
||||||
|
try:
|
||||||
|
path = os.path.join(os.environ['HOME'], ".pygtktalog")
|
||||||
|
except KeyError:
|
||||||
|
raise KeyError, "Cannot stat path for current user home!"
|
||||||
|
|
||||||
|
path = os.path.join(path, "config.ini")
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
Model.__init__(self)
|
||||||
|
self.category_tree = gtk.ListStore(gobject.TYPE_STRING)
|
||||||
|
|
||||||
|
self.refresh_ext()
|
||||||
|
return
|
||||||
|
|
||||||
|
def refresh_ext(self):
|
||||||
|
self.ext_tree = gtk.ListStore(gobject.TYPE_STRING,
|
||||||
|
gobject.TYPE_STRING)
|
||||||
|
keys = sorted(self.confd['extensions'].keys())
|
||||||
|
for i in keys:
|
||||||
|
myiter = self.ext_tree.insert_before(None,None)
|
||||||
|
self.ext_tree.set_value(myiter, 0, i)
|
||||||
|
self.ext_tree.set_value(myiter, 1, self.confd['extensions'][i])
|
||||||
|
|
||||||
|
def save(self):
|
||||||
|
try:
|
||||||
|
os.lstat(self.path)
|
||||||
|
except:
|
||||||
|
print "Saving preferences to %s." % self.path
|
||||||
|
if __debug__:
|
||||||
|
print "m_config.py: save() Saving preferences to",
|
||||||
|
print "%s" % self.path
|
||||||
|
newIni = Ini()
|
||||||
|
|
||||||
|
# main section
|
||||||
|
newIni.add_section("pyGTKtalog conf")
|
||||||
|
for opt in self.dictconf:
|
||||||
|
newIni.add_key(opt,self.confd[self.dictconf[opt]])
|
||||||
|
|
||||||
|
# recent section
|
||||||
|
newIni.add_section("pyGTKtalog recent")
|
||||||
|
|
||||||
|
count = 1
|
||||||
|
max_count = self.RECENT_MAX + 1
|
||||||
|
for opt in self.recent:
|
||||||
|
if count < max_count:
|
||||||
|
newIni.add_key(count, opt)
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
count+=1
|
||||||
|
|
||||||
|
# search history section
|
||||||
|
newIni.add_section("search history")
|
||||||
|
count = 1
|
||||||
|
max_count = self.HISTORY_MAX + 1
|
||||||
|
for opt in self.search_history:
|
||||||
|
if count < max_count:
|
||||||
|
newIni.add_key(count, opt)
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
count+=1
|
||||||
|
|
||||||
|
# extensions sections
|
||||||
|
newIni.add_section("extensions")
|
||||||
|
count = 1
|
||||||
|
for i in self.confd['extensions']:
|
||||||
|
newIni.add_key(i, self.confd['extensions'][i])
|
||||||
|
count+=1
|
||||||
|
|
||||||
|
# write config
|
||||||
|
try:
|
||||||
|
f = open(self.path, "w")
|
||||||
|
success = True
|
||||||
|
except:
|
||||||
|
if __debug__:
|
||||||
|
print "m_config.py: save() Cannot open config file",
|
||||||
|
print "%s for writing." % self.path
|
||||||
|
success = False
|
||||||
|
f.write(newIni.show())
|
||||||
|
f.close()
|
||||||
|
return success
|
||||||
|
|
||||||
|
def load(self):
|
||||||
|
try:
|
||||||
|
# try to read config file
|
||||||
|
parser = ConfigParser()
|
||||||
|
parser.read(self.path)
|
||||||
|
r = {}
|
||||||
|
h = {}
|
||||||
|
self.recent = []
|
||||||
|
self.search_history = []
|
||||||
|
for sec in parser.sections():
|
||||||
|
if sec == 'pyGTKtalog conf':
|
||||||
|
for opt in parser.options(sec):
|
||||||
|
i = self.dictconf[opt]
|
||||||
|
try:
|
||||||
|
if self.dictconf[opt] in self.dbool:
|
||||||
|
self.confd[i] = parser.getboolean(sec, opt)
|
||||||
|
elif self.dictconf[opt] in self.dstring:
|
||||||
|
self.confd[i] = parser.get(sec, opt)
|
||||||
|
else:
|
||||||
|
self.confd[i] = parser.getint(sec, opt)
|
||||||
|
except:
|
||||||
|
if __debug__:
|
||||||
|
print "m_config.py: load() failed to parse",
|
||||||
|
print "option:", opt
|
||||||
|
pass
|
||||||
|
elif sec == 'pyGTKtalog recent':
|
||||||
|
for opt in parser.options(sec):
|
||||||
|
try:
|
||||||
|
r[int(opt)] = parser.get(sec, opt)
|
||||||
|
except:
|
||||||
|
if __debug__:
|
||||||
|
print "m_config.py: load() failed to parse",
|
||||||
|
print "option:", opt
|
||||||
|
pass
|
||||||
|
elif sec == 'search history':
|
||||||
|
for opt in parser.options(sec):
|
||||||
|
try:
|
||||||
|
h[int(opt)] = parser.get(sec, opt)
|
||||||
|
except:
|
||||||
|
if __debug__:
|
||||||
|
print "m_config.py: load() failed to parse",
|
||||||
|
print "option:", opt
|
||||||
|
pass
|
||||||
|
elif sec == 'extensions':
|
||||||
|
self.confd['extensions'] = {}
|
||||||
|
for opt in parser.options(sec):
|
||||||
|
try:
|
||||||
|
self.confd['extensions'][opt] = parser.get(sec,
|
||||||
|
opt)
|
||||||
|
except:
|
||||||
|
if __debug__:
|
||||||
|
print "m_config.py: load() failed to parse",
|
||||||
|
print "option:", opt
|
||||||
|
pass
|
||||||
|
|
||||||
|
for i in range(1, self.RECENT_MAX + 1):
|
||||||
|
if i in r:
|
||||||
|
self.recent.append(r[i])
|
||||||
|
for i in range(1, self.HISTORY_MAX + 1):
|
||||||
|
if i in h:
|
||||||
|
self.search_history.append(h[i])
|
||||||
|
|
||||||
|
except:
|
||||||
|
if __debug__:
|
||||||
|
print "m_config.py: load() load config file failed"
|
||||||
|
pass
|
||||||
|
|
||||||
|
def add_recent(self, path):
|
||||||
|
if not path:
|
||||||
|
return
|
||||||
|
|
||||||
|
if path in self.recent:
|
||||||
|
self.recent.remove(path)
|
||||||
|
self.recent.insert(0,path)
|
||||||
|
return
|
||||||
|
|
||||||
|
self.recent.insert(0,path)
|
||||||
|
if len(self.recent) > self.RECENT_MAX:
|
||||||
|
self.recent = self.recent[:self.RECENT_MAX]
|
||||||
|
return
|
||||||
|
|
||||||
|
def add_search_history(self, text):
|
||||||
|
if not text:
|
||||||
|
return
|
||||||
|
|
||||||
|
if text in self.search_history:
|
||||||
|
self.search_history.remove(text)
|
||||||
|
self.search_history.insert(0, text)
|
||||||
|
return
|
||||||
|
|
||||||
|
self.search_history.insert(0, text)
|
||||||
|
if len(self.search_history) > self.HISTORY_MAX:
|
||||||
|
self.search_history = self.search_history[:self.HISTORY_MAX]
|
||||||
|
return
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
"""show prefs in string way"""
|
||||||
|
string = "[varname]\tvalue\n"
|
||||||
|
for i in self.confd:
|
||||||
|
string+="%s\t%s\n" % (i,self.confd[i])
|
||||||
|
return string
|
||||||
2002
src/models/m_main.py
Normal file
0
src/test/__init__.py
Normal file
BIN
src/test/mocks/Canon_40D.jpg
Normal file
|
After Width: | Height: | Size: 7.8 KiB |
BIN
src/test/mocks/Canon_40D_photoshop_import.jpg
Normal file
|
After Width: | Height: | Size: 9.5 KiB |
BIN
src/test/mocks/Canon_DIGITAL_IXUS_400.jpg
Normal file
|
After Width: | Height: | Size: 9.0 KiB |
BIN
src/test/mocks/Fujifilm_FinePix6900ZOOM.jpg
Normal file
|
After Width: | Height: | Size: 4.2 KiB |
BIN
src/test/mocks/Fujifilm_FinePix_E500.jpg
Normal file
|
After Width: | Height: | Size: 2.2 KiB |
BIN
src/test/mocks/Kodak_CX7530.jpg
Normal file
|
After Width: | Height: | Size: 5.8 KiB |
BIN
src/test/mocks/Konica_Minolta_DiMAGE_Z3.jpg
Normal file
|
After Width: | Height: | Size: 36 KiB |
BIN
src/test/mocks/Nikon_COOLPIX_P1.jpg
Normal file
|
After Width: | Height: | Size: 6.9 KiB |
BIN
src/test/mocks/Nikon_D70.jpg
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
src/test/mocks/Olympus_C8080WZ.jpg
Normal file
|
After Width: | Height: | Size: 3.1 KiB |
BIN
src/test/mocks/PaintTool_sample.jpg
Normal file
|
After Width: | Height: | Size: 5.6 KiB |
BIN
src/test/mocks/Panasonic_DMC-FZ30.jpg
Normal file
|
After Width: | Height: | Size: 10 KiB |
BIN
src/test/mocks/Pentax_K10D.jpg
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
src/test/mocks/Ricoh_Caplio_RR330.jpg
Normal file
|
After Width: | Height: | Size: 3.6 KiB |
BIN
src/test/mocks/Samsung_Digimax_i50_MP3.jpg
Normal file
|
After Width: | Height: | Size: 44 KiB |
BIN
src/test/mocks/Sony_HDR-HC3.jpg
Normal file
|
After Width: | Height: | Size: 3.5 KiB |
BIN
src/test/mocks/WWL_(Polaroid)_ION230.jpg
Normal file
|
After Width: | Height: | Size: 3.9 KiB |
BIN
src/test/mocks/long_description.jpg
Normal file
|
After Width: | Height: | Size: 7.4 KiB |
BIN
src/test/mocks/m.avi
Normal file
BIN
src/test/mocks/m.mkv
Normal file
BIN
src/test/mocks/m.mpg
Normal file
BIN
src/test/mocks/m.ogm
Normal file
BIN
src/test/mocks/m1.avi
Normal file
44
src/test/mocks/test_image_info.txt
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
|
||||||
|
|== INFO ON TEST IMAGES ==|
|
||||||
|
|
||||||
|
Unless stated otherwise, all images come from Wikipedia Commons - http://commons.wikimedia.org
|
||||||
|
For author and license information please refer to their respective description pages.
|
||||||
|
|
||||||
|
All images were scaled down using the GIMP v 2.4.5 since all we care about is the Exif info.
|
||||||
|
Tested before and after scaling to ensure Exif data was not modified (except for 'software' field).
|
||||||
|
|
||||||
|
|
||||||
|
== Filename == == Wiki Filename ==
|
||||||
|
|
||||||
|
= Camera Makes and Models =
|
||||||
|
Canon_40D.jpg Iguana_iguana_male_head.jpg
|
||||||
|
Canon_40D_photoshop_import.jpg Anolis_equestris_-_bright_close_3-4.jpg
|
||||||
|
Canon_DIGITAL_IXUS_400.jpg Ducati749.jpg
|
||||||
|
Fujifilm_FinePix6900ZOOM.jpg Hylidae_cinerea.JPG
|
||||||
|
Fujifilm_FinePix_E500.jpg VlaamseGaaiVeertje1480.JPG
|
||||||
|
Kodak_CX7530.jpg Red-headed_Rock_Agama.jpg
|
||||||
|
Konica_Minolta_DiMAGE_Z3.jpg Knechtova01.jpg
|
||||||
|
Nikon_COOLPIX_P1.jpg Miyagikotsu-castle6861.JPG
|
||||||
|
Nikon_D70.jpg Anolis_carolinensis_brown.jpg
|
||||||
|
Olympus_C8080WZ.jpg Pterois_volitans_Manado-e.jpg
|
||||||
|
Panasonic_DMC-FZ30.jpg Rømø_-_St.Klement_-_Kanzel_3.jpg
|
||||||
|
Pentax_K10D.jpg Mrs._Herbert_Stevens_May_2008.jpg
|
||||||
|
Ricoh_Caplio_RR330.jpg Steveston_dusk.JPG
|
||||||
|
Samsung_Digimax_i50_MP3.jpg Villa_di_Poggio_a_Caiano,_sala_neoclassica_4.JPG
|
||||||
|
Sony_HDR-HC3.jpg Positive_roll_film.jpg
|
||||||
|
WWL_(Polaroid)_ION230.jpg PoudriereBoisSousRoche3.jpg
|
||||||
|
|
||||||
|
= Other Stuff =
|
||||||
|
long_description.jpg US_10th_Mountain_Division_soldiers_in_Afghanistan.jpg
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Contributions :
|
||||||
|
|
||||||
|
PaintTool_sample.jpg -- Submitted by Jan Trofimov (OXIj)
|
||||||
|
|
||||||
|
|
||||||
|
If you come across an image which is incorrectly parsed or errors out, consider
|
||||||
|
contributing it to the test cases : ianare@gmail.com
|
||||||
|
|
||||||
|
|
||||||
45
src/test/run_tests.py
Executable file
@@ -0,0 +1,45 @@
|
|||||||
|
"""
|
||||||
|
Project: pyGTKtalog
|
||||||
|
Description: Test harvester and runner.
|
||||||
|
Type: exec
|
||||||
|
Author: Roman 'gryf' Dobosz, gryf73@gmail.com
|
||||||
|
Created: 2008-12-15
|
||||||
|
"""
|
||||||
|
import sys
|
||||||
|
import unittest
|
||||||
|
from os import path, chdir
|
||||||
|
import glob
|
||||||
|
|
||||||
|
|
||||||
|
def setup_path():
|
||||||
|
"""Sets up the python include paths to include needed directories"""
|
||||||
|
this_path = path.abspath(path.dirname(__file__))
|
||||||
|
sys.path = [path.join(this_path, "../../src")] + sys.path
|
||||||
|
sys.path = [path.join(this_path, "../../src/test/unit")] + sys.path
|
||||||
|
return
|
||||||
|
|
||||||
|
def build_suite():
|
||||||
|
"""Build suite test from files in unit directory. Filenames with test
|
||||||
|
suites should always end with "_test.py"."""
|
||||||
|
modules = []
|
||||||
|
for fname in glob.glob1('unit', '*_test.py'):
|
||||||
|
class_name = fname[:-8]
|
||||||
|
if "_" in class_name:
|
||||||
|
splited = class_name.split("_")
|
||||||
|
class_name = 'Test'
|
||||||
|
for word in splited:
|
||||||
|
class_name += word.capitalize()
|
||||||
|
else:
|
||||||
|
class_name = "Test" + class_name.capitalize()
|
||||||
|
|
||||||
|
modules.append(fname[:-3])
|
||||||
|
|
||||||
|
modules = map(__import__, modules)
|
||||||
|
load = unittest.defaultTestLoader.loadTestsFromModule
|
||||||
|
return unittest.TestSuite(map(load, modules))
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
chdir(path.abspath(path.curdir))
|
||||||
|
setup_path()
|
||||||
|
unittest.main(defaultTest="build_suite")
|
||||||
|
|
||||||
0
src/test/unit/__init__.py
Normal file
17
src/test/unit/dummy_test.py
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
"""
|
||||||
|
Project: pyGTKtalog
|
||||||
|
Description: This is simple dummy test for... testing purposes :)
|
||||||
|
Type: test
|
||||||
|
Author: Roman 'gryf' Dobosz, gryf73@gmail.com
|
||||||
|
Created: 2008-12-15
|
||||||
|
"""
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
class TestDummy(unittest.TestCase):
|
||||||
|
"""Fake test class"""
|
||||||
|
def test_dummy_method(self):
|
||||||
|
"""Test simple assertion"""
|
||||||
|
self.assertTrue(True)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
||||||
17
src/test/unit/foo_bar_test.py
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
"""
|
||||||
|
Project: pyGTKtalog
|
||||||
|
Description: This is another dummy test.
|
||||||
|
Type: test
|
||||||
|
Author: Roman 'gryf' Dobosz, gryf73@gmail.com
|
||||||
|
Created: 2008-12-15
|
||||||
|
"""
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
class TestFooBar(unittest.TestCase):
|
||||||
|
"""Fake test class"""
|
||||||
|
def test_dummy_method(self):
|
||||||
|
"""Test simple assertion"""
|
||||||
|
self.assertTrue(True)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
||||||
105
src/test/unit/video_test.py
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
"""
|
||||||
|
Project: pyGTKtalog
|
||||||
|
Description: Tests for Video class.
|
||||||
|
Type: test
|
||||||
|
Author: Roman 'gryf' Dobosz, gryf73@gmail.com
|
||||||
|
Created: 2008-12-15
|
||||||
|
"""
|
||||||
|
import unittest
|
||||||
|
import os
|
||||||
|
from lib.video import Video
|
||||||
|
|
||||||
|
class TestVideo(unittest.TestCase):
|
||||||
|
"""Class for retrive midentify script output and put it in dict.
|
||||||
|
Usually there is no need for such a detailed movie/clip information.
|
||||||
|
Video script belongs to mplayer package.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def test_avi(self):
|
||||||
|
"""test mock avi file, should return dict with expected values"""
|
||||||
|
avi = Video("mocks/m.avi")
|
||||||
|
self.assertTrue(len(avi.tags) != 0, "result should have lenght > 0")
|
||||||
|
self.assertEqual(avi.tags['audio_format'], '85')
|
||||||
|
self.assertEqual(avi.tags['width'], 128)
|
||||||
|
self.assertEqual(avi.tags['audio_no_channels'], 2)
|
||||||
|
self.assertEqual(avi.tags['height'], 96)
|
||||||
|
self.assertEqual(avi.tags['video_format'], 'XVID')
|
||||||
|
self.assertEqual(avi.tags['length'], 4)
|
||||||
|
self.assertEqual(avi.tags['audio_codec'], 'mp3')
|
||||||
|
self.assertEqual(avi.tags['video_codec'], 'ffodivx')
|
||||||
|
self.assertEqual(avi.tags['duration'], '00:00:04')
|
||||||
|
self.assertEqual(avi.tags['container'], 'avi')
|
||||||
|
|
||||||
|
def test_avi2(self):
|
||||||
|
"""test another mock avi file, should return dict with expected
|
||||||
|
values"""
|
||||||
|
avi = Video("mocks/m1.avi")
|
||||||
|
self.assertTrue(len(avi.tags) != 0, "result should have lenght > 0")
|
||||||
|
self.assertEqual(avi.tags['audio_format'], '85')
|
||||||
|
self.assertEqual(avi.tags['width'], 128)
|
||||||
|
self.assertEqual(avi.tags['audio_no_channels'], 2)
|
||||||
|
self.assertEqual(avi.tags['height'], 96)
|
||||||
|
self.assertEqual(avi.tags['video_format'], 'H264')
|
||||||
|
self.assertEqual(avi.tags['length'], 4)
|
||||||
|
self.assertEqual(avi.tags['audio_codec'], 'mp3')
|
||||||
|
self.assertEqual(avi.tags['video_codec'], 'ffh264')
|
||||||
|
self.assertEqual(avi.tags['duration'], '00:00:04')
|
||||||
|
self.assertEqual(avi.tags['container'], 'avi')
|
||||||
|
|
||||||
|
def test_mkv(self):
|
||||||
|
"""test mock mkv file, should return dict with expected values"""
|
||||||
|
avi = Video("mocks/m.mkv")
|
||||||
|
self.assertTrue(len(avi.tags) != 0, "result should have lenght > 0")
|
||||||
|
self.assertEqual(avi.tags['audio_format'], '8192')
|
||||||
|
self.assertEqual(avi.tags['width'], 128)
|
||||||
|
self.assertEqual(avi.tags['audio_no_channels'], 2)
|
||||||
|
self.assertEqual(avi.tags['height'], 96)
|
||||||
|
self.assertEqual(avi.tags['video_format'], 'mp4v')
|
||||||
|
self.assertEqual(avi.tags['length'], 4)
|
||||||
|
self.assertEqual(avi.tags['audio_codec'], 'a52')
|
||||||
|
self.assertEqual(avi.tags['video_codec'], 'ffodivx')
|
||||||
|
self.assertEqual(avi.tags['duration'], '00:00:04')
|
||||||
|
self.assertEqual(avi.tags['container'], 'mkv')
|
||||||
|
|
||||||
|
def test_mpg(self):
|
||||||
|
"""test mock mpg file, should return dict with expected values"""
|
||||||
|
avi = Video("mocks/m.mpg")
|
||||||
|
self.assertTrue(len(avi.tags) != 0, "result should have lenght > 0")
|
||||||
|
self.assertFalse(avi.tags.has_key('audio_format'))
|
||||||
|
self.assertEqual(avi.tags['width'], 128)
|
||||||
|
self.assertFalse(avi.tags.has_key('audio_no_channels'))
|
||||||
|
self.assertEqual(avi.tags['height'], 96)
|
||||||
|
self.assertEqual(avi.tags['video_format'], '0x10000001')
|
||||||
|
self.assertFalse(avi.tags.has_key('lenght'))
|
||||||
|
self.assertFalse(avi.tags.has_key('audio_codec'))
|
||||||
|
self.assertEqual(avi.tags['video_codec'], 'ffmpeg1')
|
||||||
|
self.assertFalse(avi.tags.has_key('duration'))
|
||||||
|
self.assertEqual(avi.tags['container'], 'mpeges')
|
||||||
|
|
||||||
|
def test_ogm(self):
|
||||||
|
"""test mock ogm file, should return dict with expected values"""
|
||||||
|
avi = Video("mocks/m.ogm")
|
||||||
|
self.assertTrue(len(avi.tags) != 0, "result should have lenght > 0")
|
||||||
|
self.assertEqual(avi.tags['audio_format'], '8192')
|
||||||
|
self.assertEqual(avi.tags['width'], 160)
|
||||||
|
self.assertEqual(avi.tags['audio_no_channels'], 2)
|
||||||
|
self.assertEqual(avi.tags['height'], 120)
|
||||||
|
self.assertEqual(avi.tags['video_format'], 'H264')
|
||||||
|
self.assertEqual(avi.tags['length'], 4)
|
||||||
|
self.assertEqual(avi.tags['audio_codec'], 'a52')
|
||||||
|
self.assertEqual(avi.tags['video_codec'], 'ffh264')
|
||||||
|
self.assertEqual(avi.tags['duration'], '00:00:04')
|
||||||
|
self.assertEqual(avi.tags['container'], 'ogg')
|
||||||
|
|
||||||
|
def test_capture(self):
|
||||||
|
"""test capture with some small movie"""
|
||||||
|
avi = Video("mocks/m.avi")
|
||||||
|
filename = avi.capture()
|
||||||
|
self.assertTrue(filename != None)
|
||||||
|
self.assertTrue(os.path.exists(filename))
|
||||||
|
file_size = os.stat(filename)[6]
|
||||||
|
self.assertEqual(file_size, 9077)
|
||||||
|
os.unlink(filename)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
||||||
25
src/views/__init__.py
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
# This Python file uses the following encoding: utf-8
|
||||||
|
#
|
||||||
|
# Author: Roman 'gryf' Dobosz gryf@elysium.pl
|
||||||
|
#
|
||||||
|
# Copyright (C) 2007 by Roman 'gryf' Dobosz
|
||||||
|
#
|
||||||
|
# This file is part of pyGTKtalog.
|
||||||
|
#
|
||||||
|
# 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
|
||||||
|
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
36
src/views/v_config.py
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
# This Python file uses the following encoding: utf-8
|
||||||
|
#
|
||||||
|
# Author: Roman 'gryf' Dobosz gryf@elysium.pl
|
||||||
|
#
|
||||||
|
# Copyright (C) 2007 by Roman 'gryf' Dobosz
|
||||||
|
#
|
||||||
|
# This file is part of pyGTKtalog.
|
||||||
|
#
|
||||||
|
# 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
|
||||||
|
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
|
||||||
|
from gtkmvc import View
|
||||||
|
import os.path
|
||||||
|
import lib.globs
|
||||||
|
|
||||||
|
class ConfigView(View):
|
||||||
|
"""Preferences window from glade file """
|
||||||
|
GLADE = os.path.join(lib.globs.GLADE_DIR, "config.glade")
|
||||||
|
|
||||||
|
def __init__(self, ctrl):
|
||||||
|
View.__init__(self, ctrl, self.GLADE)
|
||||||
|
return
|
||||||
|
pass # end of class
|
||||||
653
src/views/v_dialogs.py
Normal file
@@ -0,0 +1,653 @@
|
|||||||
|
# This Python file uses the following encoding: utf-8
|
||||||
|
#
|
||||||
|
# Author: Roman 'gryf' Dobosz gryf@elysium.pl
|
||||||
|
#
|
||||||
|
# Copyright (C) 2007 by Roman 'gryf' Dobosz
|
||||||
|
#
|
||||||
|
# This file is part of pyGTKtalog.
|
||||||
|
#
|
||||||
|
# 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
|
||||||
|
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
|
||||||
|
import gtk
|
||||||
|
import gobject
|
||||||
|
import os
|
||||||
|
import lib.globs
|
||||||
|
|
||||||
|
class Qst(object):
|
||||||
|
"""Show simple dialog for questions
|
||||||
|
if "OK" button pressed, return "True"
|
||||||
|
"Cancel" button return "False"
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, title="", message="", secondarymsg=""):
|
||||||
|
self.dialog = gtk.MessageDialog(
|
||||||
|
flags = gtk.DIALOG_DESTROY_WITH_PARENT,
|
||||||
|
type = gtk.MESSAGE_QUESTION,
|
||||||
|
buttons = gtk.BUTTONS_OK_CANCEL,
|
||||||
|
message_format = message,
|
||||||
|
)
|
||||||
|
self.dialog.set_title(title)
|
||||||
|
self.dialog.format_secondary_text(secondarymsg)
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
retval = self.dialog.run()
|
||||||
|
self.dialog.destroy()
|
||||||
|
if retval == gtk.RESPONSE_OK:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
class Inf(object):
|
||||||
|
"""Show simple dialog for notices"""
|
||||||
|
|
||||||
|
def __init__(self, title="", message="", secondarymsg=""):
|
||||||
|
self.dialog = gtk.MessageDialog(
|
||||||
|
flags = gtk.DIALOG_DESTROY_WITH_PARENT,
|
||||||
|
type = gtk.MESSAGE_INFO,
|
||||||
|
buttons = gtk.BUTTONS_OK,
|
||||||
|
message_format = message,
|
||||||
|
)
|
||||||
|
self.dialog.set_title(title)
|
||||||
|
self.dialog.format_secondary_text(secondarymsg)
|
||||||
|
self.dialog.connect('response',
|
||||||
|
lambda dialog, response: self.ret(response))
|
||||||
|
self.dialog.show()
|
||||||
|
|
||||||
|
def ret(self,result):
|
||||||
|
self.dialog.destroy()
|
||||||
|
return True
|
||||||
|
|
||||||
|
class Wrn(object):
|
||||||
|
"""Show simple dialog for warnings"""
|
||||||
|
|
||||||
|
def __init__(self, title="", message="", secondarymsg=""):
|
||||||
|
self.dialog = gtk.MessageDialog(
|
||||||
|
flags = gtk.DIALOG_DESTROY_WITH_PARENT,
|
||||||
|
type = gtk.MESSAGE_WARNING,
|
||||||
|
buttons = gtk.BUTTONS_CLOSE,
|
||||||
|
message_format = message,
|
||||||
|
)
|
||||||
|
self.dialog.set_title(title)
|
||||||
|
self.dialog.format_secondary_text(secondarymsg)
|
||||||
|
self.dialog.connect('response',
|
||||||
|
lambda dialog, response: self.ret(response))
|
||||||
|
self.dialog.show()
|
||||||
|
|
||||||
|
def ret(self,result):
|
||||||
|
self.dialog.destroy()
|
||||||
|
return True
|
||||||
|
|
||||||
|
class Err(object):
|
||||||
|
"""Show simple dialog for errors"""
|
||||||
|
|
||||||
|
def __init__(self, title="", message="", secondarymsg=""):
|
||||||
|
self.dialog = gtk.MessageDialog(
|
||||||
|
flags = gtk.DIALOG_DESTROY_WITH_PARENT,
|
||||||
|
type = gtk.MESSAGE_ERROR,
|
||||||
|
buttons = gtk.BUTTONS_CLOSE,
|
||||||
|
message_format = message)
|
||||||
|
|
||||||
|
self.dialog.set_title(title)
|
||||||
|
self.dialog.format_secondary_text(secondarymsg)
|
||||||
|
self.dialog.connect('response',
|
||||||
|
lambda dialog, response: self.ret(response))
|
||||||
|
self.dialog.run()
|
||||||
|
|
||||||
|
def ret(self,result):
|
||||||
|
self.dialog.destroy()
|
||||||
|
return True
|
||||||
|
|
||||||
|
class Abt(object):
|
||||||
|
"""Show simple about dialog"""
|
||||||
|
|
||||||
|
def __init__(self, name=None, ver="", title="", authors=[],licence=""):
|
||||||
|
self.dialog = gtk.AboutDialog()
|
||||||
|
self.dialog.set_title(title)
|
||||||
|
self.dialog.set_version(ver)
|
||||||
|
self.dialog.set_license(licence)
|
||||||
|
self.dialog.set_name(name)
|
||||||
|
self.dialog.set_authors(authors)
|
||||||
|
self.dialog.connect('response',
|
||||||
|
lambda dialog, response: self.dialog.destroy())
|
||||||
|
self.dialog.show()
|
||||||
|
|
||||||
|
class InputDiskLabel(object):
|
||||||
|
"""Sepcific dialog for quering user for a disc label"""
|
||||||
|
|
||||||
|
def __init__(self, label=""):
|
||||||
|
self.gladefile = os.path.join(lib.globs.GLADE_DIR, "dialogs.glade")
|
||||||
|
self.label = ""
|
||||||
|
if label!= None:
|
||||||
|
self.label = label
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
gladexml = gtk.glade.XML(self.gladefile, "inputDialog")
|
||||||
|
dialog = gladexml.get_widget("inputDialog")
|
||||||
|
entry = gladexml.get_widget("volname")
|
||||||
|
entry.set_text(self.label)
|
||||||
|
result = dialog.run()
|
||||||
|
dialog.destroy()
|
||||||
|
if result == gtk.RESPONSE_OK:
|
||||||
|
return entry.get_text()
|
||||||
|
return None
|
||||||
|
|
||||||
|
class InputNewName(object):
|
||||||
|
"""Sepcific dialog for quering user for a disc label"""
|
||||||
|
|
||||||
|
def __init__(self, name=""):
|
||||||
|
self.gladefile = os.path.join(lib.globs.GLADE_DIR, "dialogs.glade")
|
||||||
|
self.label = ""
|
||||||
|
self.name = name
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
gladexml = gtk.glade.XML(self.gladefile, "renameDialog")
|
||||||
|
dialog = gladexml.get_widget("renameDialog")
|
||||||
|
entry = gladexml.get_widget("name")
|
||||||
|
entry.set_text(self.name)
|
||||||
|
result = dialog.run()
|
||||||
|
dialog.destroy()
|
||||||
|
if result == gtk.RESPONSE_OK:
|
||||||
|
return entry.get_text()
|
||||||
|
return None
|
||||||
|
|
||||||
|
class PointDirectoryToAdd(object):
|
||||||
|
"""Sepcific dialog for quering user for selecting directory to add"""
|
||||||
|
|
||||||
|
URI="file://"+os.path.abspath(os.path.curdir)
|
||||||
|
|
||||||
|
def __init__(self,volname='',dirname=''):
|
||||||
|
self.gladefile = os.path.join(lib.globs.GLADE_DIR, "dialogs.glade")
|
||||||
|
self.gladexml = gtk.glade.XML(self.gladefile, "addDirDialog")
|
||||||
|
self.volname = self.gladexml.get_widget("dirvolname")
|
||||||
|
self.volname.set_text(volname)
|
||||||
|
self.directory = self.gladexml.get_widget("directory")
|
||||||
|
self.directory.set_text(dirname)
|
||||||
|
sigs = {"on_browse_activate":self.show_dirchooser,
|
||||||
|
"on_browse_clicked":self.show_dirchooser}
|
||||||
|
self.gladexml.signal_autoconnect(sigs)
|
||||||
|
|
||||||
|
def show_dirchooser(self,widget):
|
||||||
|
"""dialog for point the mountpoint"""
|
||||||
|
dialog = gtk.FileChooserDialog(
|
||||||
|
title="Choose directory to add",
|
||||||
|
action=gtk.FILE_CHOOSER_ACTION_OPEN,
|
||||||
|
buttons=(
|
||||||
|
gtk.STOCK_CANCEL,
|
||||||
|
gtk.RESPONSE_CANCEL,
|
||||||
|
gtk.STOCK_OPEN,
|
||||||
|
gtk.RESPONSE_OK))
|
||||||
|
|
||||||
|
dialog.set_action(gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER)
|
||||||
|
dialog.set_default_response(gtk.RESPONSE_OK)
|
||||||
|
|
||||||
|
if self.URI:
|
||||||
|
dialog.set_current_folder_uri(self.URI)
|
||||||
|
response = dialog.run()
|
||||||
|
if response == gtk.RESPONSE_OK:
|
||||||
|
self.directory.set_text(dialog.get_filename())
|
||||||
|
self.__class__.URI = dialog.get_current_folder_uri()
|
||||||
|
dialog.destroy()
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
dialog = self.gladexml.get_widget("addDirDialog")
|
||||||
|
ch = True
|
||||||
|
result = dialog.run()
|
||||||
|
while ch:
|
||||||
|
if result == gtk.RESPONSE_OK and (self.volname.get_text()=='' or \
|
||||||
|
self.directory.get_text() == ''):
|
||||||
|
a = Err("Error - pyGTKtalog",
|
||||||
|
"There are fields needed to be filled.",
|
||||||
|
"Cannot add directory without path and disc label.")
|
||||||
|
ch = True
|
||||||
|
result = dialog.run()
|
||||||
|
else:
|
||||||
|
ch = False
|
||||||
|
dialog.destroy()
|
||||||
|
if result == gtk.RESPONSE_OK:
|
||||||
|
return self.volname.get_text(),self.directory.get_text()
|
||||||
|
else:
|
||||||
|
return None,None
|
||||||
|
|
||||||
|
class SelectDirectory(object):
|
||||||
|
"""Sepcific dialog for quering user for selecting directory to add"""
|
||||||
|
|
||||||
|
URI="file://"+os.path.abspath(os.path.curdir)
|
||||||
|
|
||||||
|
def __init__(self, title=None):
|
||||||
|
if title:
|
||||||
|
self.title = title
|
||||||
|
else:
|
||||||
|
self.title = "Choose directory"
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
"""dialog for point the mountpoint"""
|
||||||
|
dialog = gtk.FileChooserDialog(
|
||||||
|
title = self.title,
|
||||||
|
action = gtk.FILE_CHOOSER_ACTION_OPEN,
|
||||||
|
buttons = (
|
||||||
|
gtk.STOCK_CANCEL,
|
||||||
|
gtk.RESPONSE_CANCEL,
|
||||||
|
gtk.STOCK_OPEN,
|
||||||
|
gtk.RESPONSE_OK))
|
||||||
|
|
||||||
|
dialog.set_action(gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER)
|
||||||
|
dialog.set_default_response(gtk.RESPONSE_OK)
|
||||||
|
|
||||||
|
retval = None
|
||||||
|
|
||||||
|
if self.URI:
|
||||||
|
dialog.set_current_folder_uri(self.URI)
|
||||||
|
response = dialog.run()
|
||||||
|
if response == gtk.RESPONSE_OK:
|
||||||
|
retval = dialog.get_filename()
|
||||||
|
self.__class__.URI = dialog.get_current_folder_uri()
|
||||||
|
dialog.destroy()
|
||||||
|
return retval
|
||||||
|
|
||||||
|
class ChooseFilename(object):
|
||||||
|
"""Dialog for quering user for selecting filename"""
|
||||||
|
|
||||||
|
URI=None
|
||||||
|
|
||||||
|
def __init__(self, path=None, title=''):
|
||||||
|
self.path = path
|
||||||
|
self.dialog = gtk.FileChooserDialog(
|
||||||
|
title="",
|
||||||
|
action=gtk.FILE_CHOOSER_ACTION_SAVE,
|
||||||
|
buttons=(
|
||||||
|
gtk.STOCK_CANCEL,
|
||||||
|
gtk.RESPONSE_CANCEL,
|
||||||
|
gtk.STOCK_SAVE,
|
||||||
|
gtk.RESPONSE_OK))
|
||||||
|
|
||||||
|
self.dialog.set_action(gtk.FILE_CHOOSER_ACTION_SAVE)
|
||||||
|
self.dialog.set_default_response(gtk.RESPONSE_OK)
|
||||||
|
self.dialog.set_do_overwrite_confirmation(True)
|
||||||
|
self.dialog.set_title(title)
|
||||||
|
|
||||||
|
f = gtk.FileFilter()
|
||||||
|
f.set_name("Catalog files")
|
||||||
|
f.add_pattern("*.sqlite")
|
||||||
|
f.add_pattern("*.sqlite.bz2")
|
||||||
|
self.dialog.add_filter(f)
|
||||||
|
f = gtk.FileFilter()
|
||||||
|
f.set_name("All files")
|
||||||
|
f.add_pattern("*.*")
|
||||||
|
self.dialog.add_filter(f)
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
if self.URI:
|
||||||
|
self.dialog.set_current_folder_uri(self.URI)
|
||||||
|
elif self.path and os.path.exists(self.path):
|
||||||
|
self.path = "file://"+os.path.abspath(self.path)
|
||||||
|
self.dialog.set_current_folder_uri(self.path)
|
||||||
|
|
||||||
|
response = self.dialog.run()
|
||||||
|
if response == gtk.RESPONSE_OK:
|
||||||
|
filename = self.dialog.get_filename()
|
||||||
|
self.__class__.URI = self.dialog.get_current_folder_uri()
|
||||||
|
self.dialog.destroy()
|
||||||
|
return filename
|
||||||
|
else:
|
||||||
|
self.dialog.destroy()
|
||||||
|
return None
|
||||||
|
pass
|
||||||
|
|
||||||
|
class ChooseDBFilename(ChooseFilename):
|
||||||
|
"""Sepcific dialog for quering user for selecting filename for database"""
|
||||||
|
|
||||||
|
URI=None
|
||||||
|
|
||||||
|
def __init__(self, path=None):
|
||||||
|
ChooseFilename.__init__(self)
|
||||||
|
self.dialog.set_title('Save catalog as...')
|
||||||
|
|
||||||
|
f = gtk.FileFilter()
|
||||||
|
f.set_name("Catalog files")
|
||||||
|
f.add_pattern("*.sqlite")
|
||||||
|
f.add_pattern("*.sqlite.bz2")
|
||||||
|
self.dialog.add_filter(f)
|
||||||
|
f = gtk.FileFilter()
|
||||||
|
f.set_name("All files")
|
||||||
|
f.add_pattern("*.*")
|
||||||
|
self.dialog.add_filter(f)
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
if self.URI:
|
||||||
|
self.dialog.set_current_folder_uri(self.URI)
|
||||||
|
elif self.path and os.path.exists(self.path):
|
||||||
|
self.path = "file://"+os.path.abspath(self.path)
|
||||||
|
self.dialog.set_current_folder_uri(self.path)
|
||||||
|
|
||||||
|
response = self.dialog.run()
|
||||||
|
if response == gtk.RESPONSE_OK:
|
||||||
|
filename = self.dialog.get_filename()
|
||||||
|
self.__class__.URI = self.dialog.get_current_folder_uri()
|
||||||
|
self.dialog.destroy()
|
||||||
|
return filename
|
||||||
|
else:
|
||||||
|
self.dialog.destroy()
|
||||||
|
return None
|
||||||
|
pass
|
||||||
|
|
||||||
|
class LoadDBFile(object):
|
||||||
|
"""Specific class for displaying openFile dialog. It has veryfication
|
||||||
|
for file existence."""
|
||||||
|
|
||||||
|
URI = None
|
||||||
|
|
||||||
|
def __init__(self, path=None):
|
||||||
|
self.path = path
|
||||||
|
|
||||||
|
self.dialog = gtk.FileChooserDialog(
|
||||||
|
title="Open catalog",
|
||||||
|
action=gtk.FILE_CHOOSER_ACTION_OPEN,
|
||||||
|
buttons=(
|
||||||
|
gtk.STOCK_CANCEL,
|
||||||
|
gtk.RESPONSE_CANCEL,
|
||||||
|
gtk.STOCK_OPEN,
|
||||||
|
gtk.RESPONSE_OK))
|
||||||
|
|
||||||
|
self.dialog.set_default_response(gtk.RESPONSE_OK)
|
||||||
|
|
||||||
|
f = gtk.FileFilter()
|
||||||
|
f.set_name("Catalog files")
|
||||||
|
f.add_pattern("*.sqlite")
|
||||||
|
f.add_pattern("*.sqlite.bz2")
|
||||||
|
self.dialog.add_filter(f)
|
||||||
|
f = gtk.FileFilter()
|
||||||
|
f.set_name("All files")
|
||||||
|
f.add_pattern("*.*")
|
||||||
|
self.dialog.add_filter(f)
|
||||||
|
|
||||||
|
def show_dialog(self):
|
||||||
|
response = self.dialog.run()
|
||||||
|
filename = None
|
||||||
|
if response == gtk.RESPONSE_OK:
|
||||||
|
try:
|
||||||
|
filename = self.dialog.get_filename()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
#self.dialog.destroy()
|
||||||
|
return 'ok',filename
|
||||||
|
else:
|
||||||
|
return 'cancel',None
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
if self.URI:
|
||||||
|
self.dialog.set_current_folder_uri(self.URI)
|
||||||
|
elif self.path and os.path.exists(self.path):
|
||||||
|
self.path = "file://"+os.path.abspath(self.path)
|
||||||
|
self.dialog.set_current_folder_uri(self.path)
|
||||||
|
|
||||||
|
res,filename = self.show_dialog()
|
||||||
|
ch = True
|
||||||
|
while ch:
|
||||||
|
if res == 'cancel':
|
||||||
|
self.dialog.destroy()
|
||||||
|
return None
|
||||||
|
try:
|
||||||
|
os.stat(filename)
|
||||||
|
self.__class__.URI = self.dialog.get_current_folder_uri()
|
||||||
|
self.dialog.destroy()
|
||||||
|
return filename
|
||||||
|
except:
|
||||||
|
a = Err("Error - pyGTKtalog","File doesn't exist.",
|
||||||
|
"The file that you choose does not exist." + \
|
||||||
|
" Choose another one, or cancel operation.")
|
||||||
|
ch = True
|
||||||
|
res, filename = self.show_dialog()
|
||||||
|
|
||||||
|
|
||||||
|
class LoadImageFile(object):
|
||||||
|
"""class for displaying openFile dialog. It have possibility of multiple
|
||||||
|
selection."""
|
||||||
|
|
||||||
|
URI="file://"+os.path.abspath(os.path.curdir)
|
||||||
|
|
||||||
|
def __init__(self, multiple=False):
|
||||||
|
self.dialog = gtk.FileChooserDialog(
|
||||||
|
title="Select image",
|
||||||
|
action=gtk.FILE_CHOOSER_ACTION_OPEN,
|
||||||
|
buttons=(
|
||||||
|
gtk.STOCK_CANCEL,
|
||||||
|
gtk.RESPONSE_CANCEL,
|
||||||
|
gtk.STOCK_OPEN,
|
||||||
|
gtk.RESPONSE_OK))
|
||||||
|
self.dialog.set_select_multiple(multiple)
|
||||||
|
self.dialog.set_default_response(gtk.RESPONSE_OK)
|
||||||
|
|
||||||
|
f = gtk.FileFilter()
|
||||||
|
f.set_name("All Images")
|
||||||
|
for i in ['*.jpg', '*.jpeg', '*.gif', '*.png', '*.tif', '*.tiff',
|
||||||
|
'*.tga', '*.pcx', '*.bmp', '*.xbm', '*.xpm', '*.jp2',
|
||||||
|
'*.jpx', '*.pnm', '*.JPG', '*.JPEG', '*.GIF', '*.PNG',
|
||||||
|
'*.TIF', '*.TIFF', '*.TGA', '*.PCX', '*.BMP', '*.XBM',
|
||||||
|
'*.XPM', '*.JP2', '*.JPX', '*.PNM']:
|
||||||
|
f.add_pattern(i)
|
||||||
|
self.dialog.add_filter(f)
|
||||||
|
f = gtk.FileFilter()
|
||||||
|
f.set_name("All files")
|
||||||
|
f.add_pattern("*.*")
|
||||||
|
self.dialog.add_filter(f)
|
||||||
|
self.preview = gtk.Image()
|
||||||
|
|
||||||
|
self.dialog.set_preview_widget(self.preview)
|
||||||
|
self.dialog.connect("update-preview", self.update_preview_cb)
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
if self.URI:
|
||||||
|
self.dialog.set_current_folder_uri(self.URI)
|
||||||
|
response = self.dialog.run()
|
||||||
|
filenames = None
|
||||||
|
only_thumbs = False
|
||||||
|
|
||||||
|
if response == gtk.RESPONSE_OK:
|
||||||
|
try:
|
||||||
|
if self.dialog.get_select_multiple():
|
||||||
|
filenames = self.dialog.get_filenames()
|
||||||
|
else:
|
||||||
|
filenames = self.dialog.get_filename()
|
||||||
|
|
||||||
|
if self.dialog.get_extra_widget().get_active():
|
||||||
|
only_thumbs = True
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
self.__class__.URI = self.dialog.get_current_folder_uri()
|
||||||
|
self.dialog.destroy()
|
||||||
|
return filenames, only_thumbs
|
||||||
|
|
||||||
|
def update_preview_cb(self, widget):
|
||||||
|
filename = self.dialog.get_preview_filename()
|
||||||
|
try:
|
||||||
|
pixbuf = gtk.gdk.pixbuf_new_from_file_at_size(filename, 128, 128)
|
||||||
|
self.preview.set_from_pixbuf(pixbuf)
|
||||||
|
have_preview = True
|
||||||
|
except:
|
||||||
|
have_preview = False
|
||||||
|
self.dialog.set_preview_widget_active(have_preview)
|
||||||
|
return
|
||||||
|
|
||||||
|
class StatsDialog(object):
|
||||||
|
"""Sepcific dialog for display stats"""
|
||||||
|
|
||||||
|
def __init__(self, values={}):
|
||||||
|
self.gladefile = os.path.join(lib.globs.GLADE_DIR, "dialogs.glade")
|
||||||
|
self.values = values
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
gladexml = gtk.glade.XML(self.gladefile, "statDialog")
|
||||||
|
dialog = gladexml.get_widget("statDialog")
|
||||||
|
|
||||||
|
if 'discs' in self.values:
|
||||||
|
entry = gladexml.get_widget("discs_entry")
|
||||||
|
entry.set_text(str(self.values['discs']))
|
||||||
|
else:
|
||||||
|
label = gladexml.get_widget("discs_label")
|
||||||
|
entry = gladexml.get_widget("discs_entry")
|
||||||
|
label.hide()
|
||||||
|
entry.hide()
|
||||||
|
|
||||||
|
if 'dirs' in self.values:
|
||||||
|
entry = gladexml.get_widget("dirs_entry")
|
||||||
|
entry.set_text(str(self.values['dirs']))
|
||||||
|
else:
|
||||||
|
label = gladexml.get_widget("dirs_label")
|
||||||
|
entry = gladexml.get_widget("dirs_entry")
|
||||||
|
label.hide()
|
||||||
|
entry.hide()
|
||||||
|
|
||||||
|
if 'files' in self.values:
|
||||||
|
entry = gladexml.get_widget("files_entry")
|
||||||
|
entry.set_text(str(self.values['files']))
|
||||||
|
|
||||||
|
if 'size' in self.values:
|
||||||
|
entry = gladexml.get_widget("size_entry")
|
||||||
|
entry.set_text(str(self.values['size']))
|
||||||
|
|
||||||
|
result = dialog.run()
|
||||||
|
dialog.destroy()
|
||||||
|
if result == gtk.RESPONSE_OK:
|
||||||
|
return entry.get_text()
|
||||||
|
return None
|
||||||
|
|
||||||
|
class TagsDialog(object):
|
||||||
|
"""Sepcific dialog for display stats"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.gladefile = os.path.join(lib.globs.GLADE_DIR, "dialogs.glade")
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
gladexml = gtk.glade.XML(self.gladefile, "tagsDialog")
|
||||||
|
dialog = gladexml.get_widget("tagsDialog")
|
||||||
|
|
||||||
|
entry = gladexml.get_widget("tag_entry1")
|
||||||
|
|
||||||
|
result = dialog.run()
|
||||||
|
|
||||||
|
dialog.destroy()
|
||||||
|
if result == gtk.RESPONSE_OK:
|
||||||
|
return entry.get_text()
|
||||||
|
return None
|
||||||
|
|
||||||
|
class TagsRemoveDialog(object):
|
||||||
|
"""Sepcific dialog for display stats"""
|
||||||
|
|
||||||
|
def __init__(self, tag_dict=None):
|
||||||
|
self.gladefile = os.path.join(lib.globs.GLADE_DIR, "dialogs.glade")
|
||||||
|
self.tag_dict = tag_dict
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
if not self.tag_dict:
|
||||||
|
return None
|
||||||
|
|
||||||
|
gladexml = gtk.glade.XML(self.gladefile, "tagRemove")
|
||||||
|
dialog = gladexml.get_widget("tagRemove")
|
||||||
|
|
||||||
|
# declare model
|
||||||
|
model = gtk.ListStore(gobject.TYPE_INT,
|
||||||
|
gobject.TYPE_STRING, gobject.TYPE_BOOLEAN)
|
||||||
|
# sort dict
|
||||||
|
values = self.tag_dict.values()
|
||||||
|
values.sort()
|
||||||
|
keys = []
|
||||||
|
for val in values:
|
||||||
|
for d_key, d_value in self.tag_dict.items():
|
||||||
|
if d_value == val:
|
||||||
|
keys.append(d_key)
|
||||||
|
|
||||||
|
# fill model with dict
|
||||||
|
for count in range(len(keys)):
|
||||||
|
myiter = model.insert_before(None, None)
|
||||||
|
model.set_value(myiter, 0, keys[count])
|
||||||
|
model.set_value(myiter, 1, values[count])
|
||||||
|
model.set_value(myiter, 2, None)
|
||||||
|
|
||||||
|
def toggle(cell, path, model):
|
||||||
|
model[path][2] = not model[path][2]
|
||||||
|
|
||||||
|
def toggle_all(column, model):
|
||||||
|
for row in model:
|
||||||
|
row[2] = not row[2]
|
||||||
|
|
||||||
|
treeview = gladexml.get_widget("treeview1")
|
||||||
|
treeview.set_model(model)
|
||||||
|
|
||||||
|
renderer = gtk.CellRendererText()
|
||||||
|
column = gtk.TreeViewColumn("Tag", renderer, text=1)
|
||||||
|
column.set_property('expand', True)
|
||||||
|
treeview.append_column(column)
|
||||||
|
|
||||||
|
renderer = gtk.CellRendererToggle()
|
||||||
|
renderer.set_property('activatable', True)
|
||||||
|
renderer.connect('toggled', toggle, model)
|
||||||
|
column = gtk.TreeViewColumn("Toggle", renderer)
|
||||||
|
column.add_attribute(renderer, "active", 2)
|
||||||
|
column.set_property('expand', False)
|
||||||
|
column.set_property("clickable", True)
|
||||||
|
column.connect("clicked", toggle_all, model)
|
||||||
|
treeview.append_column(column)
|
||||||
|
|
||||||
|
result = dialog.run()
|
||||||
|
|
||||||
|
dialog.destroy()
|
||||||
|
if result == gtk.RESPONSE_OK:
|
||||||
|
ids = []
|
||||||
|
for i in model:
|
||||||
|
if i[2]:
|
||||||
|
ids.append(i[0])
|
||||||
|
return "ok", ids
|
||||||
|
return None, None
|
||||||
|
|
||||||
|
class EditDialog(object):
|
||||||
|
"""Sepcific dialog for display stats"""
|
||||||
|
|
||||||
|
def __init__(self, values={}):
|
||||||
|
self.gladefile = os.path.join(lib.globs.GLADE_DIR, "dialogs.glade")
|
||||||
|
self.values = values
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
gladexml = gtk.glade.XML(self.gladefile, "file_editDialog")
|
||||||
|
dialog = gladexml.get_widget("file_editDialog")
|
||||||
|
|
||||||
|
filename = gladexml.get_widget("filename_entry")
|
||||||
|
filename.set_text(str(self.values['filename']))
|
||||||
|
description = gladexml.get_widget("description_text")
|
||||||
|
note = gladexml.get_widget("note_text")
|
||||||
|
|
||||||
|
if 'description' in self.values:
|
||||||
|
buff = gtk.TextBuffer()
|
||||||
|
buff.set_text(str(self.values['description']))
|
||||||
|
description.set_buffer(buff)
|
||||||
|
|
||||||
|
if 'note' in self.values:
|
||||||
|
buff = gtk.TextBuffer()
|
||||||
|
buff.set_text(str(self.values['note']))
|
||||||
|
note.set_buffer(buff)
|
||||||
|
|
||||||
|
result = dialog.run()
|
||||||
|
if result == gtk.RESPONSE_OK:
|
||||||
|
d = description.get_buffer()
|
||||||
|
n = note.get_buffer()
|
||||||
|
retval = {'filename': filename.get_text(),
|
||||||
|
'description': d.get_text(d.get_start_iter(),
|
||||||
|
d.get_end_iter()),
|
||||||
|
'note': n.get_text(n.get_start_iter(), n.get_end_iter())}
|
||||||
|
dialog.destroy()
|
||||||
|
return retval
|
||||||
|
dialog.destroy()
|
||||||
|
return None
|
||||||
68
src/views/v_image.py
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
# This Python file uses the following encoding: utf-8
|
||||||
|
#
|
||||||
|
# Author: Roman 'gryf' Dobosz gryf@elysium.pl
|
||||||
|
#
|
||||||
|
# Copyright (C) 2007 by Roman 'gryf' Dobosz
|
||||||
|
#
|
||||||
|
# This file is part of pyGTKtalog.
|
||||||
|
#
|
||||||
|
# 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
|
||||||
|
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
|
||||||
|
import gtk
|
||||||
|
|
||||||
|
class ImageView(object):
|
||||||
|
"""simple image viewer. no scaling, no zooming, no rotating.
|
||||||
|
simply show stupid image"""
|
||||||
|
|
||||||
|
def __init__(self, image_filename):
|
||||||
|
window = gtk.Window(gtk.WINDOW_TOPLEVEL)
|
||||||
|
|
||||||
|
image = gtk.Image()
|
||||||
|
image.set_from_file(image_filename)
|
||||||
|
|
||||||
|
pixbuf = image.get_pixbuf()
|
||||||
|
pic_width = pixbuf.get_width() + 23
|
||||||
|
pic_height = pixbuf.get_height() + 23
|
||||||
|
|
||||||
|
screen_width = gtk.gdk.screen_width()
|
||||||
|
screen_height = gtk.gdk.screen_height()
|
||||||
|
|
||||||
|
need_vieport = False
|
||||||
|
if pic_height > (screen_height - 128):
|
||||||
|
height = screen_height - 128
|
||||||
|
need_vieport = True
|
||||||
|
else:
|
||||||
|
height = screen_height - 128
|
||||||
|
|
||||||
|
if pic_width > (screen_width - 128):
|
||||||
|
width = screen_width - 128
|
||||||
|
need_vieport = True
|
||||||
|
else:
|
||||||
|
width = pic_width
|
||||||
|
|
||||||
|
if need_vieport:
|
||||||
|
window.resize(width, height)
|
||||||
|
viewport = gtk.ScrolledWindow()
|
||||||
|
viewport.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
|
||||||
|
viewport.add_with_viewport(image)
|
||||||
|
window.add(viewport)
|
||||||
|
else:
|
||||||
|
window.add(image)
|
||||||
|
window.show_all()
|
||||||
|
return
|
||||||
|
|
||||||
|
pass # end of class
|
||||||
44
src/views/v_main.py
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
# This Python file uses the following encoding: utf-8
|
||||||
|
#
|
||||||
|
# Author: Roman 'gryf' Dobosz gryf@elysium.pl
|
||||||
|
#
|
||||||
|
# Copyright (C) 2007 by Roman 'gryf' Dobosz
|
||||||
|
#
|
||||||
|
# This file is part of pyGTKtalog.
|
||||||
|
#
|
||||||
|
# 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
|
||||||
|
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
|
||||||
|
import os.path
|
||||||
|
import lib.globs
|
||||||
|
from gtkmvc import View
|
||||||
|
|
||||||
|
class MainView(View):
|
||||||
|
"""This handles only the graphical representation of the
|
||||||
|
application. The widgets set is loaded from glade file"""
|
||||||
|
|
||||||
|
GLADE = os.path.join(lib.globs.GLADE_DIR, "main.glade")
|
||||||
|
|
||||||
|
def __init__(self, ctrl):
|
||||||
|
View.__init__(self, ctrl, self.GLADE)
|
||||||
|
|
||||||
|
# hide v2.0 features
|
||||||
|
self['separatormenuitem4'].hide()
|
||||||
|
self['list1'].hide()
|
||||||
|
self['thumbnails1'].hide()
|
||||||
|
return
|
||||||
|
|
||||||
|
pass # end of class
|
||||||