././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1745680285.2341115 tryton-7.0.24/0000755000175000017500000000000015003173635011256 5ustar00cedced././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1745680282.0 tryton-7.0.24/CHANGELOG0000644000175000017500000005142015003173632012467 0ustar00cedced Version 7.0.24 - 2025-04-26 --------------------------- * Bug fixes (see mercurial logs for details) Version 7.0.23 - 2025-04-02 --------------------------- * Bug fixes (see mercurial logs for details) Version 7.0.22 - 2025-03-04 --------------------------- * Bug fixes (see mercurial logs for details) Version 7.0.21 - 2025-02-16 --------------------------- * Bug fixes (see mercurial logs for details) Version 7.0.20 - 2025-01-01 --------------------------- * Bug fixes (see mercurial logs for details) Version 7.0.19 - 2024-12-16 --------------------------- * Bug fixes (see mercurial logs for details) Version 7.0.18 - 2024-12-01 --------------------------- * Bug fixes (see mercurial logs for details) Version 7.0.17 - 2024-11-06 --------------------------- * Bug fixes (see mercurial logs for details) Version 7.0.16 - 2024-10-18 --------------------------- * Bug fixes (see mercurial logs for details) Version 7.0.15 - 2024-10-05 --------------------------- * Bug fixes (see mercurial logs for details) Version 7.0.14 - 2024-09-16 --------------------------- * Bug fixes (see mercurial logs for details) Version 7.0.13 - 2024-09-01 --------------------------- * Bug fixes (see mercurial logs for details) Version 7.0.12 - 2024-08-01 --------------------------- * Bug fixes (see mercurial logs for details) Version 7.0.11 - 2024-07-01 --------------------------- * Bug fixes (see mercurial logs for details) Version 7.0.10 - 2024-05-01 --------------------------- * Bug fixes (see mercurial logs for details) Version 7.0.9 - 2024-04-17 -------------------------- * Bug fixes (see mercurial logs for details) Version 7.0.8 - 2024-04-04 -------------------------- * Bug fixes (see mercurial logs for details) Version 7.0.7 - 2024-03-03 -------------------------- * Bug fixes (see mercurial logs for details) Version 7.0.6 - 2024-02-15 -------------------------- * Bug fixes (see mercurial logs for details) Version 7.0.5 - 2024-01-15 -------------------------- * Bug fixes (see mercurial logs for details) Version 7.0.4 - 2024-01-01 -------------------------- * Bug fixes (see mercurial logs for details) Version 7.0.3 - 2023-12-16 -------------------------- * Bug fixes (see mercurial logs for details) Version 7.0.2 - 2023-11-17 -------------------------- * Bug fixes (see mercurial logs for details) Version 7.0.1 - 2023-11-03 -------------------------- * Bug fixes (see mercurial logs for details) Version 7.0.0 - 2023-10-30 -------------------------- * Bug fixes (see mercurial logs for details) * Add support for Python 3.12 * Support create record from autocomplete * Support scanning code * Reset search offset when domain has changed * Add a canonicalize function for domains * Support PYSON comparison of timedelta * Support encoding timedelta into PYSON TimeDelta * Transfer configuration, profiles and plugins from previous version * Eagerly read string value of multiselection fields * Use locale timezone for CSV in locale format * Display sum on column header Version 6.8.0 - 2023-05-01 -------------------------- * Bug fixes (see mercurial logs for details) * Include events to logs * Add option to disable thread * Remove support for Python 3.7 * Add support for Python 3.11 * Save and Restore tree state on List Form * Do record (pre)validation and saving on selection change for list-form views * Eagerly read string value of selection fields * Do not select first record by default * Use ``&`` and ``|`` as boolean operator in search widget * Use toggle button and label on translate dialog * Manage domain on id in single value list as unique * Do not validate domain nor enforce uniqueness for empty fields unless they're required or invisible * Display the number of selected records on xxx2Many Version 6.6.0 - 2022-10-31 -------------------------- * Bug fixes (see mercurial logs for details) * Support authentication services Version 6.4.0 - 2022-05-02 -------------------------- * Bug fixes (see mercurial logs for details) * Use unittest discover * Support notification message * Add option to define logging output location * Remove TreeViewControl * Manage optional column * Default CSV encoding to UTF8 with BOM * Display the number of selected records * Humanize the count result * Add limit to search_count * Call view_get for board view * Limit board action domain to active id and ids * Add support for Python 3.10 * Remove support for Python 3.6 * Manage creatable attribute of view Version 6.2.0 - 2021-11-01 -------------------------- * Bug fixes (see mercurial logs for details) * Support grouping attribute * Support monetary and symbol field attributes * Support digits from relation field * Allow PYSON Expression as key for PYSON In with dict object * Support Binary field in CSV import/export Version 6.0.0 - 2021-05-03 -------------------------- * Bug fixes (see mercurial logs for details) * Support printing zip archive * Add direct print to UNIX * Add attachment preview * Support document widget * Support icon of type URL * Allow sending email with existing attachments * Add paths and model context to action data * Add indentation in CSV export of tree * Manage which records to use for actions * Handle device cookie * Add breadcrumb as title of window form * Display revision on dialog * Execute report asynchronously * Add support for Python 3.9 * Support empty value for timedelta converter * Add interactive search on tree view * Unify PYSON string format Version 5.8.0 - 2020-11-02 -------------------------- * Bug fixes (see mercurial logs for details) * Remove support for Python 3.5 * Format timedelta in CSV export * Manage symbol widget * Fallback to model name as title * Manage deletable and writable state * Support e-mail template * Send e-mail via the server * Support PYSON comparison of date and datetime * Position copied records based on order * Allow configuration of default colors for graph and calendar * Use existing context for get_preferences * Add context to export URL Version 5.6.0 - 2020-05-04 -------------------------- * Bug fixes (see mercurial logs for details) * Support link button on form * Add URL for the current export * Add option to export listed records * Retire App Menu * Add support for Python 3.8 * Manage readonly dates in calendar view * Manage editable on calendar view * Manage model access on calendar view * Set client title at the end of header bar * Manage named separator as label * Add MultiSelection entry to Dict field * Position new record based on order * Allow relation field to be selected in CSV export * Support validate attribute of wizard's Button Version 5.4.0 - 2019-11-04 -------------------------- * Bug fixes (see mercurial logs for details) * Support visual context on tree view * Support multiselection field * Support dot notation on PYSON Eval * Order Dict keys by sequence * Support relation fields in domain parser * Add Dict widget for tree view * Add handle to reordable line of tree views Version 5.2.0 - 2019-05-06 -------------------------- * Bug fixes (see mercurial logs for details) * Add domain inversion for reference fields * Add list-form view * Remove support for Python 3.4 * Replace ColorSelectionDialog by ColorChooserDialog * Support HTML widget * Use locale format for data import and export * Add day view on calendar * Add CSV exports into print toolbar * Display unread and total of notes * Support badge on icon * Use resources method * Replace Arrow by Image * Remove Fast Tabbing option * Remove usage of focus_chain * Add shortcuts for switch and remove on One2Many * Rename roundup.url configuration to bug.url * Remove send error to bug tracker * Replace Table by Grid * Use separate XML parser in Views * Use between numbers in filter box * Rename email and calendar file * Use markup for styling * Replace ImageMenuItem by MenuItem * Replace ComboBoxEntry by ComboBox or ComboBoxText * Replace GObject.idle_add by GLibe.idle_add * Remove positional arguments for PyGObject * Remove pygtkcompat * Implement Reference cell with Selection and Many2One cells * Replace CellRendererBinary with clickable pixbuf cells * Allow cell to have multiple renderers * Fill 'to' with 'from' value on filter box * Add tab domain in URL * Use Popover for search filter * Display version on login dialog * Use tab key for tab navigation * Use ShortcutsWindow * Improve default size of widgets and dialog * Use record and field properties of widget instead of method arguments * Reduce offset when no search result * Ensure URL scheme of bus (issue7792) * Add drag & drop support on binary widget Version 5.0.0 - 2018-10-01 -------------------------- * Bug fixes (see mercurial logs for details) * Use dropdown for attachment * Apply factor on domain parser * Use tab name in CSV Export/Import * Add bus management * Use CSS to style label * New icons * Support timestamp field * Add support for Python 3.7 * Use GtkApplication * Validate dictionary items * Add (i)like support to domain inversion * Use Python 3 * Improve loading strategy by using views * Use non conflicting keyboard shortcuts in sync with sao * Use the context to get the suffix of window name * Call on_change methods after setting default dtstart on calendar view Version 4.8.0 - 2018-04-23 -------------------------- * Bug fixes (see mercurial logs for details) * Manage active field on search widget * Reset record to its original state when discarding the popup window * Add support of expand attribute on group tag * Add the context model name in the screen context * Use limit to expand tree * Add keyword attribute to button tag * Support field name on image tag * Add option to check new version * Show related record names for all windows * Remove support of GTK+ 2.0 * Add icons on Many2One in editable tree * Use translated values when exporting Reference and Selection fields Version 4.6.0 - 2017-10-30 -------------------------- * Bug fixes (see mercurial logs for details) * Allow to export model and record name of Reference fields * Re-position buttons on Binary/Image widget * Improve treeview headers * Update states of both toolbar and menu * Improve toolbar order * Make readonly, required and invalid widget themable * Load user CSS theme * Use profile name or login details in title Version 4.4.0 - 2017-05-01 -------------------------- * Bug fixes (see mercurial logs for details) * Verify certificate with default CA * Replace URL entry by a toolbar button Version 4.2.0 - 2016-11-28 -------------------------- * Bug fixes (see mercurial logs for details) * Add support for GTK+ 3.0 * Add PYSON Widget * Show records names in wizard title * Add support for datetime_field to Reference * Show non editable widget as insensitive * Move info-bar at the bottom * Manage readonly on fields translation dialog * Restore tab default order when no manual change * Call autocompletion when setting record value * Remove database management * Limit readonly state for xxx2Many * Show records names in relate window title * Add support for count on Action Window Domains * Manage custom login process * Add clear icon on Many2One * Re-position icons on Many2One widget Version 4.0.0 - 2016-05-02 -------------------------- * Bug fixes (see mercurial logs for details) * Improvement of charts design * Timedelta uses 30 days for month * Add all available encoding for import/export * Add CSV parameters to export window * Add DnD on import window * Manage context model of ir.action.act_window * Add Note * Manage view_ids on tree view * Use Apple key for copy/past of list Version 3.8.0 - 2015-11-02 -------------------------- * Bug fixes (see mercurial logs for details) * Use italic labels for editable fields * Escape '_' in button string and page string * Export selected records only * Use HTML for RichText widget * Change Progressbar to work with float between 0 and 1 * Add Fast Tabbing option * Remove colors on widgets * Bold label of required fields * Explicit error message for invalid record * Add reversed operators to PYSON expressions Version 3.6.0 - 2015-04-20 -------------------------- * Bug fixes (see mercurial logs for details) * Hide columns containing always the same value * Remove Tabs Position option * Manage product attribute * Remove float_time widget * Add TimeDelta field * Improve date/time widgets * Remove datetime widget on list/tree view * Allow to put many times the same field on tree view * Add search completion on dictionary widget * Merge host and port field in profile editor * New color scheme for graph * Replace img_{width,height} by width and height attributes Version 3.4.0 - 2014-10-20 -------------------------- * Bug fixes (see mercurial logs for details) * Add DnD on export window * Allow to overide predefined export * Prefill export window with current view fields * Manage field context on Group value * Add export of selection string * Load plugins from local user directory * Manage tree_state attribute * Add simple tree_state support on form view * Change range operator of search widget to be included * Explicitly set value of parent field Version 3.2.0 - 2014-04-21 -------------------------- * Bug fixes (see mercurial logs for details) * Add option to show revisions * Add a multi selection widget for many2many * Remove auto-refresh * Add support of domain for non-relation field * Allow drag & drop on the attachment button * Replace sha widget by password * Add Len to PYSON * Use a pool of connection * Manage client actions from button and wizard * Add tree_invisible attribute to button in tree view * Add buttons of the view in actions menu * Don't evaluate anymore relate action with the record * Paste on editable list create new records if needed * Drop support of Python 2.6 * Allow to search on rec_name of Reference fields * Use local timezone * Sanitize report file extension Version 3.0.0 - 2013-10-21 -------------------------- * Bug fixes (see mercurial logs for details) * Add factor on number widgets * Add calendar view * Add URL entry * Remove request Version 2.8.0 - 2013-04-22 -------------------------- * Bug fixes (see mercurial logs for details) * Manage dynamic label * Manage prefix, suffix on tree view * Manage selection_change_with on Selection and Reference fields * Add Dict fields on form * Remove Goto * Add global search * Add toggle button for menu * Replace shortcuts by menu favorites * Move Plugins into toolbar Actions * Add url to list view * Add dynamic icon for url * Add completion on Many2One, Many2Many and One2Many * Add bookmark on search widget * Manage domains on Action Window * Use range for Date/Time fields in filter box * Allow multi-selection for Selection field in filter box Version 2.6.0 - 2012-10-22 -------------------------- * Bug fixes (see mercurial logs for details) * Allow to paste in editable list view * Manage readonly state on group * Remove "Please wait" box * Refactorize date widgets * Manage tuple on Reference * Add constant interpolation on line graph * Make Import/Export Data no-modal * Deactivate switch view button when there is only 1 view * Manage create/delete field access * Add dynamic size limit on the One2Many, Many2Many and Char * Search only on fields in XML view * Cache action keyword * Always use cached views * Manage model access * Manage time format * Deactivate attachment button when record is not created Version 2.4.0 - 2012-04-23 -------------------------- * Bug fixes (see mercurial logs for details) * Remove workflow * Improve contextual menu: - Actions for all relation fields on list view - Use icons - Add attachments entry * Use RPCProgress almost everywhere * Add a simple search box * New domain parser using shlex * Better translation dialog box * Add the richtext widget for WYSIWYG Editor on text fields * Add a record pool to prefetch more records * Add Time widget * Add support for fuzzy_translation * Activate save button on editing * Refactor set/get and set/get_client of fields * Display binary size alongside the buttons in treeview * Set correctly the focus on tab switching * Add shortcut to focus search entry * Add binary field to tree views * Improve board view: - use the same search widget as form - link double click to open popup Version 2.2.0 - 2011-10-24 -------------------------- * Bug fixes (see mercurial logs for details) * New search widget * Improve memory management of Binary fields * Support buffer for Binary fields * Remove delete on Escape in editable tree * Use JSON-RPC * Limit size of field when possible * Add xalign and yalign as fields attributes * Convert many popup to be no-modal * Add window manager for: - replace current window - prevent simmilar window * Merge and review toolbars in form and board * Drop support of Python 2.5 * Use the same design for Many2Many than One2Many * Allow resize columns smaller than the header Version 2.0.0 - 2011-04-26 -------------------------- * Bug fixes (see mercurial logs for details) * Popup form dialog has 3 buttons (close, ok, new) * New UI layout * Add DnD on tree view * Merge tree and list views * Remove generic default value on right-click * Made numpad locale aware * Selection widget used for many2one dynamically change their content following the domain specification * Add open button on binary and image widgets * Hide buttons on image widget if readonly * Added a connection manager à la gajim * Fix warning in wizards * Added possibility to use server-side icons * Added additional gtk.Entry for filename on BinaryField * Display deleted lines in One2Many and Many2Many * Handle URL * Add communication between boards * Added domain inversion feature * Handle loading attribute on fields * Use default format for value in wizard form * Add One2One field Version 1.8.0 - 2010-11-01 -------------------------- * Bug fixes (see mercurial logs for details) * More fully integrate GTK menubar for Mac OS * Allow to configure search limit * Add Previous/Next on list view * Set non-applicable form controls in one2many view to be insensitive Version 1.6.0 - 2010-05-08 -------------------------- * Bug fixes (see mercurial logs for details) * Don't stop wizard execution when exception occurs * Use ir.attachment view instead of a custom one * Add fingerprint and CA check for SSL connection * Use lazy load in Import/Export windows * Validate record before switching view * Refactoring: * Better naming in model * Group extends list * Add an index to Group * New common windows for dialog of many2one, one2many and many2many * one2many and many2many dialog use the same screen than the widget * Reduce the number of option in Screen * Remove RPCProxy to handle logout exception on every server call * Better naming of event signals * Fix on_change detection for many2many for issue1400 * Add PySON to replace python string evaluation * Don't show "Spell Checking" option if gtkspell is not present * Use the same internal model for many2many and one2many fields * Remove egenix-mx-base and replace it by python-dateutil * Add cache to safe_eval * Use versioned configuration directory * Next and Previous scroll per page on list and don't loop * Add AccelGroup on search windows (CTRL+Return) * Use same keyboard shortcut for xxx2many than for other fields Version 1.4.0 - 2009-10-19 -------------------------- * Bug fixes (see mercurial logs for details) * Handle datetime_field in xxx2Many * Add new safe_eval * Ask previous password for set_preferences on password change * Add "Statusbar" option * Add default filename for database backup * Add checkbox on restore to update database * Add 'login.host' options to hide server connection * Handle required attribute with local domains * Allow to run wizard in tabs * Remove statusbar on form for more space * Add "Change Accelerators" option * Use gzip in pysocket * Use the report name to create the temporary file to open it * Allow to store wizard size * Add reset default on fields * Store in config default width and height of main window * Added arrow navigation if supported by gtk * Add 'starts with' and 'ends with' on char search * Handle domain with '=' or '!=' as operator on selection * Extend fields domain with local domains * Improve float time widget to handle year, month, week and day and handle float_time attribute for contextual time convertion Version 1.2.0 - 2009-04-20 -------------------------- * Bug fixes (see mercurial logs for details) * Make graph works also with datetime * Add edition on Many2Many * Fix open in csv export to use file actions * Update client labels at language change * Handle datetime_field in Many2One * Set readonly on records with _datetime in the context * Display values of reference fields even if there is no model * Allow to directly print or create email with reports * Handle invisible states on list view * Add user warnings * Add Model in logs * Allow to duplicate many records at once * Improve netrpc communication speed * Improve date widget to display mask only when having focus * Fix for host with IPv6 enable but without default IPv6 route * Add desktop entry * Add win32 single executable * Allow egg installation Version 1.0.0 - 2008-11-17 -------------------------- * Initial release ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1745680282.0 tryton-7.0.24/COPYRIGHT0000644000175000017500000000212215003173632012543 0ustar00cedcedCopyright (C) 2004-2008 Tiny SPRL. Copyright (C) 2007-2009 Lorenzo Gil Sanchez. Copyright (C) 2007-2013 Bertrand Chenal. Copyright (C) 2007-2025 Cédric Krier. Copyright (C) 2008-2011 Udo Spallek. Copyright (C) 2008-2011 virtual things - Preisler & Spallek GbR. Copyright (C) 2008-2025 B2CK SPRL. Copyright (C) 2010-2024 Nicolas Évrard. Copyright (C) 2011-2012 Rodrigo Hübner. Copyright (C) 2012-2013 Antoine Smolders. Copyright (C) 2020-2021 Maxime Richez Copyright (C) 2020-2021 SALUC SA 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 . ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685599.0 tryton-7.0.24/LICENSE0000644000175000017500000010451314517761237012301 0ustar00cedced GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. 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. Copyright (C) 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 . 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: Copyright (C) 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 . 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 . ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685598.0 tryton-7.0.24/MANIFEST.in0000644000175000017500000000051714517761236013030 0ustar00cedcedinclude LICENSE include COPYRIGHT include README.rst include CHANGELOG include setup.nsi include setup-single.nsi include setup-bundle.sh include tryton.desktop include *.nsh include setup-freeze.py include make-*-installer.sh include */gtk-3.0/* include tryton/data/pixmaps/tryton/LICENSE include tryton/data/sounds/LICENSE graft doc ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1745680285.2341115 tryton-7.0.24/PKG-INFO0000644000175000017500000000545115003173635012360 0ustar00cedcedMetadata-Version: 2.4 Name: tryton Version: 7.0.24 Summary: Tryton desktop client Home-page: http://www.tryton.org/ Download-URL: http://downloads.tryton.org/7.0/ Author: Tryton Author-email: foundation@tryton.org License: GPL-3 Project-URL: Bug Tracker, https://bugs.tryton.org/ Project-URL: Documentation, https://docs.tryton.org/latest/client-desktop/ Project-URL: Forum, https://www.tryton.org/forum Project-URL: Source Code, https://code.tryton.org/tryton Keywords: business application ERP Platform: any Classifier: Development Status :: 5 - Production/Stable Classifier: Environment :: X11 Applications :: GTK Classifier: Framework :: Tryton Classifier: Intended Audience :: End Users/Desktop Classifier: License :: OSI Approved :: GNU General Public License (GPL) Classifier: Natural Language :: Bulgarian Classifier: Natural Language :: Catalan Classifier: Natural Language :: Chinese (Simplified) Classifier: Natural Language :: Czech Classifier: Natural Language :: Dutch Classifier: Natural Language :: English Classifier: Natural Language :: Finnish Classifier: Natural Language :: French Classifier: Natural Language :: German Classifier: Natural Language :: Hungarian Classifier: Natural Language :: Indonesian Classifier: Natural Language :: Italian Classifier: Natural Language :: Persian Classifier: Natural Language :: Polish Classifier: Natural Language :: Portuguese (Brazilian) Classifier: Natural Language :: Romanian Classifier: Natural Language :: Russian Classifier: Natural Language :: Slovenian Classifier: Natural Language :: Spanish Classifier: Natural Language :: Turkish Classifier: Natural Language :: Japanese Classifier: Natural Language :: Ukrainian Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.8 Classifier: Programming Language :: Python :: 3.9 Classifier: Programming Language :: Python :: 3.10 Classifier: Programming Language :: Python :: 3.11 Classifier: Programming Language :: Python :: 3.12 Classifier: Topic :: Office/Business Requires-Python: >=3.8 License-File: LICENSE Requires-Dist: pycairo Requires-Dist: python-dateutil Requires-Dist: PyGObject<3.51,>=3.19 Provides-Extra: calendar Requires-Dist: GooCalendar>=0.7; extra == "calendar" Provides-Extra: sound Requires-Dist: playsound; extra == "sound" Dynamic: author Dynamic: author-email Dynamic: classifier Dynamic: description Dynamic: download-url Dynamic: home-page Dynamic: keywords Dynamic: license Dynamic: license-file Dynamic: platform Dynamic: project-url Dynamic: provides-extra Dynamic: requires-dist Dynamic: requires-python Dynamic: summary tryton ====== The desktop client of Tryton. Tryton is business software, ideal for companies of any size, easy to use, complete and 100% Open Source. It provides modularity, scalability and security. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685599.0 tryton-7.0.24/README.rst0000644000175000017500000000031114517761237012752 0ustar00cedcedtryton ====== The desktop client of Tryton. Tryton is business software, ideal for companies of any size, easy to use, complete and 100% Open Source. It provides modularity, scalability and security. ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1745680285.1374438 tryton-7.0.24/bin/0000755000175000017500000000000015003173635012026 5ustar00cedced././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1725720314.0 tryton-7.0.24/bin/tryton0000755000175000017500000000410014667063372013321 0ustar00cedced#!/usr/bin/env python3 import os import sys try: DIR = os.path.abspath(os.path.normpath(os.path.join(__file__, '..', '..', 'tryton'))) if os.path.isdir(DIR): sys.path.insert(0, os.path.dirname(DIR)) except NameError: pass if hasattr(sys, 'frozen'): prefix = os.path.dirname(sys.executable) share = os.path.join(prefix, 'share') os.environ['GTK_EXE_PREFIX'] = prefix os.environ['GTK_DATA_PREFIX'] = prefix os.environ['EV_BACKENDS_DIR'] = os.path.join( prefix, 'lib', 'evince', '4', 'backends') os.environ['XDG_DATA_DIRS'] = share os.environ['GDK_PIXBUF_MODULE_FILE'] = os.path.join( share, 'gtk-3.0', 'gdk-pixbuf.loaders') os.environ['GTK_IM_MODULE_FILE'] = os.path.join( share, 'gtk-3.0', 'gtk.immodules') os.environ['GI_TYPELIB_PATH'] = os.path.join( prefix, 'lib', 'girepository-1.0') os.environ.setdefault('SSL_CERT_FILE', os.path.join(share, 'ssl', 'cert.pem')) os.environ.setdefault('SSL_CERT_DIR', os.path.join(share, 'ssl', 'certs')) if sys.platform == 'win32': # cx_freeze >= 5 put python modules under lib directory # and dependencies of gdk-pixbuf loaders sys.path.append(os.path.join(prefix, 'lib')) sys.path.append(os.path.join( prefix, 'lib', 'gdk-pixbuf-2.0', '2.10.0', 'loaders')) # On first launch the MacOSX app launcher may append an extra unique # argument starting with -psn_. This must be filtered to not crash the # option parser. if sys.platform == 'darwin': sys.argv = [a for a in sys.argv if not a.startswith('-psn_')] if os.environ.get('FLATPAK_ID') and os.environ.get('XDG_RUNTIME_DIR'): # Set $TMPDIR to share files outside the sandbox os.environ['TMPDIR'] = os.path.join( os.environ.get('XDG_RUNTIME_DIR'), 'app', os.environ.get('FLATPAK_ID')) # Disable dbusmenu to show second menu in tabs os.environ['UBUNTU_MENUPROXY'] = '0' # overlay-scrollbar breaks treeview height os.environ['LIBOVERLAY_SCROLLBAR'] = '0' from tryton.client import main main() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685599.0 tryton-7.0.24/catalan.nsh0000644000175000017500000000211514517761237013404 0ustar00cedced;This file is part of Tryton. The COPYRIGHT file at the top level of ;this repository contains the full copyright notices and license terms. !verbose 3 !ifdef CURLANG !undef CURLANG !endif !define CURLANG ${LANG_CATALAN} LangString LicenseText ${CURLANG} "Tryton està alliberat sota la llicència «GNU General Public License» publicada per la Free Software Foundation, o bé la versió 3 de la llicència, o (sota la vostra elecció) qualsevol versió posterior. Llegiu detingudament la llicència. Premeu «Següent» per continuar." LangString LicenseNext ${CURLANG} "&Següent" LangString PreviousInstall ${CURLANG} "Tryton ja està instal·lat.$\n$\nPremeu `OK` per eliminar la versió anterior o `Cancel·la` per cancel·lar l'actualització." LangString SecTrytonName ${CURLANG} "Tryton" LangString SecTrytonDesc ${CURLANG} "Instal·la tryton.exe i altres fitxers necessaris" LangString SecStartMenuName ${CURLANG} "Accessos directes al menú d'inici i a l'escriptori" LangString SecStartMenuDesc ${CURLANG} "Crea accessos directes al menú d'inici i a l'escriptori" ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1745680285.140777 tryton-7.0.24/doc/0000755000175000017500000000000015003173635012023 5ustar00cedced././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1725720314.0 tryton-7.0.24/doc/conf.py0000644000175000017500000000425614667063372013344 0ustar00cedced# This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. import os base_url = os.environ.get('DOC_BASE_URL') if base_url: modules_url = base_url + '/modules-{module}/' trytond_url = base_url + '/server/' else: modules_url = 'https://docs.tryton.org/${series}/modules-{module}/' trytond_url = 'https://docs.tryton.org/${series}/server/' def get_info(): import subprocess import sys module_dir = os.path.dirname(os.path.dirname(__file__)) info = dict() result = subprocess.run( [sys.executable, 'setup.py', '--name', '--description'], stdout=subprocess.PIPE, check=True, cwd=module_dir) info['name'], info['description'] = ( result.stdout.decode('utf-8').strip().splitlines()) result = subprocess.run( [sys.executable, 'setup.py', '--version'], stdout=subprocess.PIPE, check=True, cwd=module_dir) version = result.stdout.decode('utf-8').strip() major_version, minor_version, _ = version.split('.', 2) major_version = int(major_version) minor_version = int(minor_version) if minor_version % 2: info['series'] = 'latest' info['branch'] = 'branch/default' else: info['series'] = '.'.join(version.split('.', 2)[:2]) info['branch'] = 'branch/' + info['series'] return info info = get_info() html_theme = 'sphinx_book_theme' html_theme_options = { 'repository_provider': 'gitlab', 'repository_url': 'https://code.tryton.org/tryton', 'repository_branch': info['branch'], 'use_source_button': True, 'use_edit_page_button': True, 'use_repository_button': True, 'use_download_button': False, 'path_to_docs': 'tryton/doc', } html_title = info['description'] master_doc = 'index' project = info['name'] release = version = info['series'] default_role = 'ref' highlight_language = 'none' extensions = [ 'sphinx_copybutton', 'sphinx.ext.intersphinx', ] intersphinx_mapping = { 'python': ('https://docs.python.org/', None), } linkcheck_ignore = [r'/.*', r'https://demo.tryton.org/*'] del get_info, info, base_url, modules_url, trytond_url ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685599.0 tryton-7.0.24/doc/glossary.rst0000644000175000017500000001354614517761237014443 0ustar00cedcedGlossary ######## .. glossary:: :sorted: Actions An *action* is a function which is triggered by a user intervention. *Actions* are called from activating menu items or pushing buttons. Actions often provide :term:`wizards`. Board The *board* is a type of :term:`views` able to handle other views. This view type is not documented or not used for now. Character Encoding See [WP-ENCOD]_ CSV File format for Comma Separated Values. See [WP-CSV]_ Data *Data* means information content produced by users. Dialog A *dialog* is a :term:`popup` window, which overlays other windows and request user interaction. *Dialogs* are used to set up special :term:`actions`. Fields *Fields* are attributes of a *data object*. *Fields* are represented as table fields in relational databases. Form View The *form* is a mode of :term:`views`, which displays single :term:`records` of data. Form The *form* is the general type of :term:`views` used in Tryton. The *form* provides several modes for presenting :term:`data`: * :term:`Form View` * :term:`Tree View` * :term:`Graph View` Graph View *Graph view* is a mode of :term:`views` to show sets of data in a diagram. *Graph views* can be pie-charts or bar-charts. Main Frame The *main frame* is a huge part arranged in the center of the :term:`Tryton client`. *Using the Tryton client* means mainly using the *main frame* part. It contains :term:`tabs` to organize and to show different :term:`views`. Model A *model* describes how data is represented and accessed. Models formally define records and relationships for a certain domain of interest. Modules *Modules* are enclosed file packages for the :term:`Tryton server`. A *Module* defines the :term:`Model`, the presentation of the information (:term:`views`), functions, :term:`actions` and default presets. Additionally *modules* may provide standardized data like ISO names for countries. *Modules* in Tryton are build up generically. That is, they are constructed as simple as possible to provide the desired functionality. Plugins A *plugin* is an add-on module for the :term:`Tryton client`. Popup A small window which pops up the main window. Records A *record* is a singular dataset in a :term:`Model`. *Records* are represented as lines or *records* in a relational database table. Tabs *Tabs* are :term:`widgets` to arrange different contents side by side. They are used to switch quickly between different domains of interest. Tryton uses *tabs* in two layer: * A tabbed :term:`Main Frame`. * Tabs inside :term:`Views`. The main frame consists of *tabs* that embed the main menu and all views to an appropriate :term:`model`. The other type of *tabs* is used inside of :term:`views` to split them into visual domains of the same model. These *tabs* are used for structuring contents of one model to different sub headings. Three-Tiers A *three-tiers* application framework like Tryton, is build up of three different software components: 1. The storage or data tier. 2. The logic or application tier. 3. The presentation tier. The storage tier in the Tryton framework is provided by the PostgreSQL database engine. The application logic tier is provided by :term:`Tryton server` and its :term:`modules`. The presentation tier is mainly provided by the :term:`Tryton client`. In a *three tiers* framework, the presentation tier (client) never connects directly to the storage tier. All communication is controlled by the application tier. Tree View *Tree view* is a mode of :term:`views` showing sets of :term:`data`. *Tree views* can be flat lists or tables as well as tree-like nested lists. Tryton Server The *Tryton server* is the application or logic tier in the :term:`three-tiers` application platform *Tryton*. The *Tryton server* connects the underlying application logic of the different :term:`modules` with corresponding database records. The *Tryton server* provides different interfaces to present the generated information: * :term:`Tryton client`: (graphical user interface GUI) * XMLRPC see [WP-XMLRPC]_ * WebDAV see [WP-WebDAV]_ * OpenOffice Tryton Client The *Tryton client* application is the graphical user interface (GUI) of the :term:`Tryton server`. Views A *view* is the visual presentation of :term:`data`. *Views* resides inside :term:`tabs` in the :term:`main frame` of the :term:`Tryton client`. There are two general types of *views* in Tryton: 1. :term:`Form` 2. :term:`Board` Each of the view types has different modes to show data. *Views* are built of several :term:`widgets` and provide often additional :term:`actions`. It is also possible to present the same data in different view modes alternately. Widgets A *Widget* is a visual element of a graphical user interface (GUI). Some *Widgets* solely show information, others allow manipulation from user side. Example *Widgets* are buttons, check-boxes, entry-boxes, selection lists, tables, lists, trees, ... Wizards *Wizards* define stateful sequences of interaction to proceed complex :term:`actions`. A *wizard* divides the complexity of some actions into several user guided steps. References ********** .. [WP-XMLRPC] http://en.wikipedia.org/wiki/Xmlrpc .. [WP-WebDAV] http://en.wikipedia.org/wiki/Webdav .. [WP-CSV] http://en.wikipedia.org/wiki/Comma-separated_values .. [WP-ENCOD] http://en.wikipedia.org/wiki/Character_encoding ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685598.0 tryton-7.0.24/doc/index.rst0000755000175000017500000000044214517761236013700 0ustar00cedced===================== Tryton Desktop Client ===================== Tryton is a Graphical User Interface to Tryton based on GTK__ and Python__. __ http://www.gtk.org __ http://www.python.org Contents ======== .. toctree:: :maxdepth: 2 installation usage glossary releases ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685599.0 tryton-7.0.24/doc/installation.rst0000644000175000017500000000126014517761237015267 0ustar00cedcedInstalling tryton ================= Installation ------------ There are three options to install Tryton: * Install the version provided by your operating system distribution. This is the quickest and recommended option for those who has operating system that distributes Tryton. * Install the published package. You first need to have `pip `_ installed. Then to install ``tryton`` run: .. code-block:: console $ python -m pip install tryton * Without installation, you need to make sure you have all the dependencies installed and then run: .. code-block:: console $ python bin/tryton ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685599.0 tryton-7.0.24/doc/releases.rst0000644000175000017500000000013214517761237014366 0ustar00cedced.. _releases-index: ============= Release notes ============= .. include:: ../CHANGELOG ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685599.0 tryton-7.0.24/doc/requirements-doc.txt0000644000175000017500000000004414517761237016062 0ustar00cedcedsphinx_book_theme sphinx_copybutton ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739470965.0 tryton-7.0.24/doc/usage.rst0000644000175000017500000004544014753434165013701 0ustar00cedced :tocdepth: 2 Client Usage ############ This document is the reference about the concepts of the graphical user interface (also known as *Tryton client* ) of the Tryton application framework. Name **** tryton - Graphical user client of the Tryton application framework Synopsis ******** .. code-block:: console $ tryton [options] [url] On startup the login dialog is displayed. Options ******* --version Show program version number and exit -h, --help Show help message and exit -c FILE, --config=FILE Specify alternate `configuration file`_ -d, --dev Enable development mode, which deactivates client side caching -v, --verbose Enable basic debugging -l LOG_LEVEL, --log-level=LOG_LEVEL Specify the log level: DEBUG, INFO, WARNING, ERROR, CRITICAL -o LOG_OUTPUT, --log-ouput=LOG_OUTPUT Specify the file used to output logging information -u LOGIN, --user=LOGIN Specify the login user -s SERVER, --server=SERVER Specify the server hostname:port URL *** When an url is passed, the client will try to find already running client that could handle it and send to this one to open the url. If it doesn't find one then it will start the GUI and open the url itself. The url schemes are: ``tryton://[:]//model/[/][;parameters]`` ``tryton://[:]//wizard/[;parameters]`` ``tryton://[:]//report/[;parameters]`` ``parameters`` the corresponding fields of actions encoded in `JSON`_ .. _JSON: http://en.wikipedia.org/wiki/Json .. Note:: ``model`` is for ``window`` .. Note:: ``report`` must have at least a data parameter with ``ids``, ``id`` and ``model name`` Overview ******** On startup the login dialog is displayed. It allows to select a existing profile (or to manage them) or to enter the host and database information. The following schematic illustration of the Tryton client shows the names of all important visual parts. Figure: Tryton client application:: Client Window ________________________________________________________________ |T| Search | Favorites Tryton _ o x| |----------------------------------------------------------------| | + | ______ | Tabs | |-+ | [Tab1] |[Tab2]| [Tab3]... | | | |- | +-------+ +--------------------------------+| | + | | Menu Tab2 || | |-+ | |-----------------------------------------------|| Tool bar | | |- | | New Save Switch Reload | Prev Next | Attach v || | | |- | |-----------------------------------------------|| | + | | _______________________ || Search widget | |-+ | | Filter | *| Bookmark <- -> || | | |- | |-----------------------------------------------|| | | |- | | || | + | | || View | |-+ | | || | |- | | || | |- | | || | | | || | | | || | | | || | | | || |_____________| |_______________________________________________|| |________________________________________________________________| Tabbed Main Frame ^^^^^^^^^^^^^^^^^ This part of the client contains all the related contents and functions provided by the :term:`Tryton server` :term:`modules`. All aspects inside the *main frame* depend at least on the individual set of activated modules. The main frame provides a `tabbed document interface`__ to arrange different views side by side. New :term:`tabs` are opened by special :term:`actions`, like choosing a menu item or clicking some action buttons. All tabs include titles which show the name of the provided view. :term:`Tabs` can be arranged by Drag and Drop. __ TDI_ .. _TDI: http://en.wikipedia.org/wiki/Tabbed_document_interface .. Note:: Inside :term:`views` there can be tabs, too. Menu ++++ The *menu* does not contain fixed menu items. All of them are dynamically provided by the actual set of the activated :term:`modules` depending on the access rules of the current user. If a menu item is clicked, the appropriate action will open in a new tab. A search field allows to quickly filter the menu items by name and to search in models for which the global search is enabled. Application Menu **************** The following section describes the action of the application menu. A rule of thumb: All items of the menu bar that are suffixed by three dots (...) will open an intermediate :term:`dialog` for setting up the provided menu action. Most dialog provide a *Cancel* button, used to stop the complete dialog process. .. _Menu-Preferences: Preferences: A preference dialog opens, where the actual user can show and edit his personal settings. All user preferences are stored server side. I.e. logging in with the same credentials from different computers always restores the same preferences. Options ^^^^^^^ The Options menu sets up several visual and context depending preferences. .. _Menu-Options-Toolbar: Toolbar: * Default: Shows labels and icons as defaulted in the GTK configuration. * Text and Icons: Shows labels and icons in the tool bar. * Icons: Shows icons only in the tool bar. * Text: Shows labels only in the tool bar. .. _Menu-Options-Form: Form: * Save Column Width: Check box to enable saving of manually adjusted widths of columns in lists and trees. * Save Tree Expanded State: Check box to enable saving of expanded and selected nodes in trees/lists. * Spell Checking: Check box to enable spell checking in fields. * Play Sound for Code Scanner: Check box to enable sound on success or failure of code scan. .. _Menu-Options-PDA-Mode: PDA Mode: When activated, the client display in a condensed mode. .. _Menu-Options-Search-Limit: Search Limit: Open a dialog to set up the maximum number of records displayed on a list. .. _Menu-Options-Check_Version: Check Version: Check box to enable the check of new bug-fix version. Help ^^^^ .. _Menu-Help-Keyboard_Shortcuts: Keyboard Shortcuts...: Shows the information dialog of the predefined keyboard shortcut map. * Edition Widgets: Shows shortcuts working on text entries, relation entries and date/time entries. .. _Menu-Help-About: About...: License, Contributors, Authors of Tryton Tool Bar ******** The tool bar contains the functionalities linked to the current tab. Some operations are working with one record or with a selection of :term:`records`. In :term:`form view` the actual record is selected for operations. In :term:`tree view` all selected records are used for operations. .. _Toolbar-New: New: Creates a new record. .. _Toolbar-Save: Save: Saves the actual record. .. _Toolbar-Switch_View: Switch View: Switches the actual view aspect to: * :term:`Form view` * :term:`Tree view` * :term:`Graph view` Not all views provide all aspects. .. _Toolbar-Reload_Undo: Reload/Undo: Reloads the content of the actual tab. Undoes changes, if save request for the current record is denied. .. _Toolbar-Duplicate: Duplicate: Duplicates the content of the actual record in a newly created record. .. _Toolbar-Delete: Delete: Deletes the selected or actual record. .. _Toolbar-Previous: Previous: Goes to the last record in a list (sequence). .. _Toolbar-Next: Next: Goes to the next record in a list (sequence). .. _Toolbar-Search: Search: Goes to the search widget. .. _Toolbar-View_Logs: View Logs...: Shows generic information of the current record. .. _Toolbar-Show revisions: Show revisions...: Reload the current view/record at a specific revision. .. _Toolbar-Close: Close Tab: Closes the current tab. A Request :term:`Dialog` opens in case of unsaved changes. .. _Toolbar-Attachment: Attachment: The attachment item handles the document management system of Tryton which is able to attach files to any arbitrary :term:`model`. On click it opens the attachments :term:`dialog`. The default dialog shows a list view of the attached files and links. .. _Toolbar-Actions: Actions...: Shows all actions for the actual view, model and record. .. _Toolbar-Relate: Relate...: Shows all relate view for the actual view, model and record. .. _Toolbar-Report: Report...: Shows all reports for the actual view, model and record. .. _Toolbar-Email: E-Mail...: Open an editor to send an email related to the actual record. .. _Toolbar-Print: Print...: Shows all print actions for the actual view, model and record. .. _Toolbar-Copy-URL: Copy URL: Copy the URL of the form into the clipboard. .. _Toolbar-Export_Data: Export Data...: Export of current/selected records into :term:`CSV`-file or open it in Excel. * Predefined exports - Choose preferences of already saved exports. * All Fields: Fields available from the model. * Fields to export: Defines the specific fields to export. * Options: - Save: Save export as a CSV file. - Open: Open export in spread sheet application. * Add field names: Add a header row with field names to the export data. * Actions: - Add: Adds selected fields to *Fields to export*. - Remove: Removes selected fields from *Fields to export*. - Clear: Removes all fields from *Fields to export*. - Save Export: Saves field mapping to a *Predefined export* with a name. - Delete Export: Deletes a selected *Predefined export*. - OK: Exports the data (action depending on *Options*). - Cancel .. _Toolbar-Import_Data: Import Data...: Import records from :term:`CSV`-file. * All Fields: Fields available in the model (required fields are marked up). * Fields to Import: Exact sequence of all columns in the CSV file. * File to Import: File :term:`dialog` for choosing a CSV file to import. * CSV Parameters: Setup specific parameters for chosen CSV file. - Field Separator: Character which separates CSV fields. - Text Delimiter: Character which encloses text in CSV. - Encoding: :term:`Character encoding` of CSV file. - Lines to Skip: Count of lines to skip a headline or another offset. * Actions: - Add: Adds fields to *Fields to Import*. - Remove: Deletes fields from *Fields to Import*. - Clear: Removes all fields from *Fields to Import*. - Auto-Detect: Tries to auto detect fields in the CSV *File to Import*. - OK: Proceeds the data import. - Cancel Widgets ******* There are a several widgets used on Tryton in client side. The follow sections will explains some of them. Date/DateTime/Time Widgets ^^^^^^^^^^^^^^^^^^^^^^^^^^ These widgets have several key shortcuts to quickly modify the value. Each key increases if lower case or decreases if upper case: - ``y``: by one year - ``m``: by one month - ``w``: by one week - ``d``: by one day - ``h``: by one hour - ``i``: by one minute - ``s``: by one second The ``=`` key sets the widget value to the current date and time. TimeDelta Widgets ^^^^^^^^^^^^^^^^^ This widget represent a duration using different symbol of time separated by space: - ``Y``: for years (default: 365 days) - ``M``: for months (default: 30 days) - ``w``: for weeks (default: 7 days) - ``d``: for days (default: 24 hours) - ``h``: for hours (default: 60 minutes) - ``m``: for minutes (default: 60 seconds) - ``s``: for seconds (default: 1 seconds) The hours, minutes and seconds are also represented as ``H:M:s``. For example: ``2w 3d 4:30`` which represents: two weeks, three days and four and an half hours. The value of each symbol may be changed by the context of the widget. For example, a day could be configured as 8 hours. Search Widget ^^^^^^^^^^^^^ The search widget adds the ability to easily search for records on the current tab. This widget is visible only on :term:`tree view`. The Syntax ++++++++++ A query is composed of search clauses. A clause is composed of a field name (with ``:`` at the end), an operator and a value. The field name is optional and defaults to the record name. The operator is also optional and defaults to ``like`` or ``equal`` depending on the type of the field. The default operator is ``=`` except for fields of type ``char``, ``text`` and ``many2one`` which is ``ilike``. Field Names +++++++++++ All field names shown in the :term:`tree view` can be searched. Field names must be followed by a ``:``. For example: ``Name:`` If the field name contains spaces, it is possible to escape it using double quotes. For example: ``"Receivable Today":`` Operators +++++++++ The following operators can be used: * ``=``: equal to * ``<``: less then * ``<=``: less then or equal to * ``>``: greater then * ``>=``: greater then or equal to * ``!=``: not equal * ``!``: not equal or not like (depending of the type of field) For example: ``Name: != Dwight`` .. note:: The ``ilike`` operator is never explicit and ``%`` is appended to the value to make it behaves like ``starts with``. Values ++++++ The format of the value depends on the type of the field. A list of values can be set using ``;`` as separator. For example: ``Name: Michael; Pam`` It will find all records with ``Name`` equal to ``Michael`` or ``Pam``. A range of number values can be set using ``..``. For example: ``Amount: 100..500`` It will find all records with ``Amount`` between ``100`` and ``500`` included. There are two wildcards: * ``%``: matches any string of zero or more characters. * ``_``: matches any single character. It is possible to escape special characters in values by using double quotes. For example: ``Name: "Michael:Scott"`` Here it will search with the value ``Michael:Scott``. Clause composition ++++++++++++++++++ The clauses can be composed using the two boolean operators ``&`` (for `logical conjunction`_) and ``|`` (for `logical disjunction`_). By default, there is an implicit ``&`` between each clause if no operator is specified. For example: ``Name: Michael Amount: 100`` is the same as ``Name: Michael & Amount: 100`` The ``&`` operator has a highest precedence than ``|`` but you can change it by using parenthesis. For example: ``(Name: Michael | Name: Pam) & Amount: 100`` is different than ``Name: Michael | Name: Pam & Amount: 100`` which is evaluated as ``Name: Michael | (Name: Pam & Amount: 100)`` .. _logical conjunction: https://en.wikipedia.org/wiki/Logical_conjunction .. _logical disjunction: https://en.wikipedia.org/wiki/Logical_disjunction RichText Editor ^^^^^^^^^^^^^^^ This feature create a rich text editor with various features that allow for text formatting. The features are: * Bold: On/off style of bold text * Italic: On/off style of italic text * Underline: On/off style of underline text * Choose font family: Choice from a combo box the desired font family * Choose font size: Choice from a combo box the desired size font * Text justify: Choice between four options for alignment of the line (left, right, center, fill) * Background color: Choose the background color of text from a color palette * Foreground color: Choose the foreground color of text from a color palette Besides these features, it can change and edit text markup. The text markup feature has a similar HTML tags and is used to describe the format specified by the user and is a way of storing this format for future opening of a correct formatted text. The tags are explain follows: * Bold: Tag ``b`` is used, i.e. text * Italic: Tag ``i`` is used, i.e. text * Underline: Tag ``u`` is used, i.e. text * Font family: It is a attribute ``font-family`` for ``span`` tag, i.e. text * Font size: It is a attribute ``size`` for ``span`` tag, i.e. text * Text Justify: For justification text is used paragraph tag ``p``. The paragraph tag is used to create new lines and the alignment is applied across the board. Example:

some text

* Background color: It is a attribute ``background`` for ``span`` tag, i.e. text * Foreground color: It is a attribute ``foreground`` for ``span`` tag, i.e. text CSS *** The client can be styled using the file :file:`theme.css`. Here are the list of custom selectors: * ``.readonly``: read only widget or label * ``.required``: widget or label of required field * ``.invalid``: widget for which the field value is not valid * ``headerbar.profile-``: the name of the connection profile is set on the main window For more information about style option see `GTK+ CSS`_ .. _GTK+ CSS: https://developer.gnome.org/gtk3/stable/chap-css-overview.html Appendix ******** Configuration File ^^^^^^^^^^^^^^^^^^ General configuration :file:`~/.config/tryton/x.y/tryton.conf` Accelerators configuration :file:`~/.config/tryton/x.y/accel.map` Fingerprints :file:`~/.config/tryton/x.y/known_hosts` .. warning:: When switching a connection from encrypted to non-encrypted vice-versa, make sure to remove the corresponding host line in the ``known_hosts`` file. Also check this file when the client cannot connect to the server. `Certification Authority `_ :file:`~/.config/tryton/x.y/ca_certs` .. note:: If the server is using a custom certificate authority, you must put all the certificates of the chain in the ``ca_certs``. Profile configuration :file:`~/.config/tryton/x.y/profiles.cfg` Local user plugins directory :file:`~/.config/tryton/x.y/plugins` Custom CSS theme :file:`~/.config.tryton/x.y/theme.css` .. note:: ``~`` means the home directory of the user. But on Windows system it is the ``APPDATA`` directory. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685599.0 tryton-7.0.24/english.nsh0000644000175000017500000000173014517761237013434 0ustar00cedced;This file is part of Tryton. The COPYRIGHT file at the top level of ;this repository contains the full copyright notices and license terms. !verbose 3 !ifdef CURLANG !undef CURLANG !endif !define CURLANG ${LANG_ENGLISH} LangString LicenseText ${CURLANG} "Tryton is released under 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. Please carefully read the license. Click Next to continue." LangString LicenseNext ${CURLANG} "&Next" LangString PreviousInstall ${CURLANG} "Tryton is already installed.$\n$\nClick `OK` to remove the previous version or `Cancel` to cancel this upgrade." LangString SecTrytonName ${CURLANG} "Tryton" LangString SecTrytonDesc ${CURLANG} "Install tryton.exe and other required files" LangString SecStartMenuName ${CURLANG} "Start Menu and Desktop Shortcuts" LangString SecStartMenuDesc ${CURLANG} "Create shortcuts in the start menu and on desktop" ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685599.0 tryton-7.0.24/farsi.nsh0000644000175000017500000000340414517761237013107 0ustar00cedced;This file is part of Tryton. The COPYRIGHT file at the top level of ;this repository contains the full copyright notices and license terms. !verbose 3 !ifdef CURLANG !undef CURLANG !endif !define CURLANG ${LANG_FARSI} LangString LicenseText ${CURLANG} "*1'*HF *-* E,H2 , ~ 'D 3 (F'/ F1E 'A2'1G' "2'/EF*41 4/G '3*  D7A' EA'/ E,H2 1' (' /B* E7'D9G A1E'&/. (1' '/'EG (9/ 1' D F/." LangString LicenseNext ${CURLANG} "&(9/" LangString PreviousInstall ${CURLANG} "Tryton /1 -'D -'61 F5( 4/G '3*.$\n$\n (1' -0A F3.G B(D ' D:H (1' D:H 'F '1*B'!  OK 1' D F/." LangString SecTrytonName ${CURLANG} "*1'*HF" LangString SecTrytonDesc ${CURLANG} "F5( A'D *1'*HF ',1' H 3'1 ED2HE'* F1E 'A2'1" LangString SecStartMenuName ${CURLANG} "EFH 41H9 H E'F(1G' /3*'~" LangString SecStartMenuDesc ${CURLANG} "D7A'K E'F(1 G' EFH 41H9 H /3*'~ 1' ','/ F/" ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685599.0 tryton-7.0.24/french.nsh0000644000175000017500000000210114517761237013241 0ustar00cedced;This file is part of Tryton. The COPYRIGHT file at the top level of ;this repository contains the full copyright notices and license terms. !verbose 3 !ifdef CURLANG !undef CURLANG !endif !define CURLANG ${LANG_FRENCH} LangString LicenseText ${CURLANG} "Tryton est publié sous la GNU General Public License comme publiée par la Free Software Foundation, soit la version 3 de la License, ou (à votre choix) toute version ultérieure. S'il vous plaît lisez attentivement la license. Cliquez sur Suivant pour continuer." LangString LicenseNext ${CURLANG} "&Suivant" LangString PreviousInstall ${CURLANG} "Tryton est déjà installé.$\n$\nCliquez `OK` pour supprimer la précédente version ou `Annuler` pour annuler cette mis à jour." LangString SecTrytonName ${CURLANG} "Tryton" LangString SecTrytonDesc ${CURLANG} "Installe tryton.exe et d'autres fichiers requis" LangString SecStartMenuName ${CURLANG} "Raccourcis dans le menu Démarrer et sur le bureau" LangString SecStartMenuDesc ${CURLANG} "Crée les raccourcis dans le menu Démarrer et sur le bureau" ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685598.0 tryton-7.0.24/german.nsh0000644000175000017500000000207214517761236013253 0ustar00cedced;This file is part of Tryton. The COPYRIGHT file at the top level of ;this repository contains the full copyright notices and license terms. !verbose 3 !ifdef CURLANG !undef CURLANG !endif !define CURLANG ${LANG_GERMAN} LangString LicenseText ${CURLANG} "Tryton wird unter der GNU General Public License wie von der Free Software Foundation veröffentlicht freigegeben, entweder Version 3 der Lizenz oder (nach Ihrer Wahl) jeder späteren Version. Bitte lesen Sie die Lizenz aufmerksam. Klicken Sie auf Weiter, um fortzufahren." LangString LicenseNext ${CURLANG} "&Weiter" LangString PreviousInstall ${CURLANG} "Tryton ist bereits installiert.$\n$\nWählen Sie `OK`, um die bisherige Version zu entfernen oder `Abbrechen` um die Aktualisierung abzubrechen." LangString SecTrytonName ${CURLANG} "Tryton" LangString SecTrytonDesc ${CURLANG} "tryton.exe und andere benötigte Dateien installieren" LangString SecStartMenuName ${CURLANG} "Startmenü und Desktop-Verknüpfungen" LangString SecStartMenuDesc ${CURLANG} "Verknüpfungen im Startmenü und auf dem Desktop erstellen" ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1730238497.0 tryton-7.0.24/make-darwin-installer.sh0000644000175000017500000000150714710254041016002 0ustar00cedced#!/bin/sh set -e version=`./setup.py --version` rm -rf build dist mkdir -p darwin/gtk-3.0 GDK_PIXBUF_MODULEDIR="$(dirname $(which gdk-pixbuf-query-loaders))/../lib/gdk-pixbuf-2.0/2.10.0/loaders" \ gdk-pixbuf-query-loaders \ | sed -e '/^#/d' \ | sed -e 's@/.*/lib/gdk-pixbuf-2.0@\@executable_path/lib/gdk-pixbuf-2.0@' \ > darwin/gtk-3.0/gdk-pixbuf.loaders gtk-query-immodules-3.0 \ | sed -e '/^#/d' \ | sed -e 's@/.*/lib/gtk-3.0@\@executable_path/lib/gtk-3.0@' \ > darwin/gtk-3.0/gtk.immodules ./setup.py compile_catalog ./setup-freeze.py bdist_mac mkdir dist mv build/Tryton.app dist/ for f in CHANGELOG COPYRIGHT LICENSE; do cp ${f} dist/${f}.txt done cp -r doc dist/ rm -f "tryton-${version}.dmg" hdiutil create "tryton-${version}.dmg" -volname "Tryton Client ${version}" \ -fs HFS+ -srcfolder dist ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1730238497.0 tryton-7.0.24/make-win32-installer.sh0000644000175000017500000000075614710254041015465 0ustar00cedced#!/bin/sh set -e version=`./setup.py --version` series=${version%.*} bits=`python -c "import platform; print(platform.architecture()[0])"` rm -rf build dist mkdir -p win32/gtk-3.0 gdk-pixbuf-query-loaders | sed -e '/^#/d' > win32/gtk-3.0/gdk-pixbuf.loaders gtk-query-immodules-3.0 | sed -e '/^#/d' > win32/gtk-3.0/gtk.immodules ./setup.py compile_catalog ./setup-freeze.py install_exe -d dist cp `which gdbus.exe` dist/ makensis -DVERSION=${version} -DSERIES=${series} -DBITS=${bits} setup.nsi ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685599.0 tryton-7.0.24/portuguese.nsh0000644000175000017500000000203414517761237014203 0ustar00cedced;This file is part of Tryton. The COPYRIGHT file at the top level of ;this repository contains the full copyright notices and license terms. !verbose 3 !ifdef CURLANG !undef CURLANG !endif !define CURLANG ${LANG_ENGLISH} LangString LicenseText ${CURLANG} "Tryton é licenciado sob a General Public License conforme publicada pela Free Software Foundation, conforme a versão 3 da licença, ou (segundo sua escolha) qualquer versão posterior. Favor ler a licença cuidadosamente. Clique em Seguinte para continuar." LangString LicenseNext ${CURLANG} "&Seguinte" LangString PreviousInstall ${CURLANG} "O Tryton já está instalado.$\n$\nClique em 'OK' para remover a versão anterior ou 'Cancelar' para cancelar esta atualização." LangString SecTrytonName ${CURLANG} "Tryton" LangString SecTrytonDesc ${CURLANG} "Instala tryton.exe e outros arquivos necessários" LangString SecStartMenuName ${CURLANG} "Menu Iniciar e Atalhos da Área de Trabalho" LangString SecStartMenuDesc ${CURLANG} "Criar atalhos no menu iniciar e na área de trabalho" ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1725720314.0 tryton-7.0.24/setup-freeze.py0000755000175000017500000001073314667063372014270 0ustar00cedced#!/usr/bin/env python3 # This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. import glob import os import re import ssl import sys import tempfile from subprocess import PIPE, Popen, check_call from cx_Freeze import Executable, setup from setuptools import find_packages home = os.path.expanduser('~/') pythonrc = os.path.join(home, '.pythonrc.py') try: with open(pythonrc) as fp: exec(fp.read()) except IOError: pass include_files = [ (os.path.join(sys.prefix, 'share', 'glib-2.0', 'schemas'), os.path.join('share', 'glib-2.0', 'schemas')), (os.path.join(sys.prefix, 'lib', 'gtk-3.0'), os.path.join('lib', 'gtk-3.0')), (os.path.join(sys.prefix, 'lib', 'gdk-pixbuf-2.0'), os.path.join('lib', 'gdk-pixbuf-2.0')), (os.path.join(sys.prefix, 'lib', 'evince'), os.path.join('lib', 'evince')), (os.path.join(sys.prefix, 'share', 'icons', 'Adwaita'), os.path.join('share', 'icons', 'Adwaita')), (os.path.join(sys.platform, 'gtk-3.0', 'gtk.immodules'), os.path.join('share', 'gtk-3.0', 'gtk.immodules')), (os.path.join(sys.platform, 'gtk-3.0', 'gdk-pixbuf.loaders'), os.path.join('share', 'gtk-3.0', 'gdk-pixbuf.loaders')), ] required_gi_namespaces = [ 'Atk-1.0', 'EvinceDocument-3.0', 'EvinceView-3.0', 'GLib-2.0', 'GModule-2.0', 'GObject-2.0', 'Gdk-3.0', 'GdkPixbuf-2.0', 'Gio-2.0', 'GooCanvas-[2-3].0', 'Gtk-3.0', 'HarfBuzz-0.0', 'Pango-1.0', 'PangoCairo-1.0', 'PangoFT2-1.0', 'Rsvg-2.0', 'cairo-1.0', 'fontconfig-2.0', 'freetype2-2.0', ] def replace_path(match): libs = [os.path.basename(p) for p in match.group(1).split(',')] required_libs.update(libs) if sys.platform == 'darwin': prefix = '@executable_path/lib' else: prefix = 'lib' libs = [os.path.join(prefix, l) for l in libs] return 'shared-library="%s"' % ','.join(libs) lib_re = re.compile(r'shared-library="([^\"]*)"') required_libs = set() temp = tempfile.mkdtemp() for ns in required_gi_namespaces: gir_name = '%s.gir' % ns gir_file = glob.glob( os.path.join(sys.prefix, 'share', 'gir-1.0', gir_name))[0] gir_name = os.path.basename(gir_file) ns = os.path.splitext(gir_name)[0] gir_tmp = os.path.join(temp, gir_name) with open(gir_file, 'r', encoding='utf-8') as src: with open(gir_tmp, 'w', encoding='utf-8') as dst: for line in src: dst.write(lib_re.sub(replace_path, line)) typefile_name = '%s.typelib' % ns typefile_file = os.path.join('lib', 'girepository-1.0', typefile_name) typefile_tmp = os.path.join(temp, typefile_name) check_call(['g-ir-compiler', '--output=' + typefile_tmp, gir_tmp]) include_files.append((typefile_tmp, typefile_file)) if sys.platform == 'win32': required_libs.update([ 'libepoxy-0.dll', ]) lib_path = os.getenv('PATH', os.defpath).split(os.pathsep) else: lib_path = [os.path.join(sys.prefix, 'lib')] for lib in required_libs: for path in lib_path: path = os.path.join(path, lib) if os.path.isfile(path): break else: raise Exception('%s not found' % lib) include_files.append((path, os.path.join('lib', lib))) ssl_paths = ssl.get_default_verify_paths() include_files.append( (ssl_paths.openssl_cafile, os.path.join('share', 'ssl', 'cert.pem'))) if os.path.exists(ssl_paths.openssl_capath): include_files.append( (ssl_paths.openssl_capath, os.path.join('share', 'ssl', 'certs'))) version = Popen( 'python3 ./setup.py --version', stdout=PIPE, shell=True, encoding='utf-8' ).stdout.read() version = version.strip() setup(name='tryton', version=version, options={ 'build_exe': { 'no_compress': True, 'include_files': include_files, 'excludes': ['tkinter'], 'silent': True, 'packages': find_packages() + ['gi'], 'include_msvcr': True, }, 'bdist_mac': { 'iconfile': os.path.join( 'tryton', 'data', 'pixmaps', 'tryton', 'tryton.icns'), 'bundle_name': 'Tryton', } }, executables=[Executable( 'bin/tryton', base='Win32GUI' if sys.platform == 'win32' else None, icon=os.path.join( 'tryton', 'data', 'pixmaps', 'tryton', 'tryton.ico'), )]) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1745680285.2374449 tryton-7.0.24/setup.cfg0000644000175000017500000000073615003173635013105 0ustar00cedced[extract_messages] copyright_holder = Tryton msgid_bugs_address = issue_tracker@tryton.org output_file = tryton/data/locale/tryton.pot keywords = _, gettext [init_catalog] domain = tryton input_file = tryton/data/locale/tryton.pot output_dir = tryton/data/locale [compile_catalog] domain = tryton directory = tryton/data/locale [update_catalog] domain = tryton input_file = tryton/data/locale/tryton.pot output_dir = tryton/data/locale [egg_info] tag_build = tag_date = 0 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685599.0 tryton-7.0.24/setup.nsi0000644000175000017500000001327614517761237013154 0ustar00cedced;This file is part of Tryton. The COPYRIGHT file at the top level of ;this repository contains the full copyright notices and license terms. ;Check version !ifndef VERSION !error "Missing VERSION! Specify it with '/DVERSION='" !endif ;Check series !ifndef SERIES !error "Missing SERIES! Specify if with '/DSERIES='" !endif ;Check bits !ifndef BITS !error "Missing BITS! Specify it with '/DBITS='" !endif ;Include Modern UI !include "MUI.nsh" ;General Name "Tryton ${BITS} ${VERSION}" OutFile "tryton-${BITS}-${VERSION}.exe" SetCompressor lzma SetCompress auto Unicode true ;Default installation folder InstallDir "$PROGRAMFILES\Tryton-${BITS}-${SERIES}" ;Get installation folder from registry if available InstallDirRegKey HKCU "Software\Tryton-${BITS}-${SERIES}" "" BrandingText "Tryton ${BITS} ${SERIES}" ;Vista redirects $SMPROGRAMS to all users without this RequestExecutionLevel admin ;Variables Var MUI_TEMP Var STARTMENU_FOLDER ;Interface Settings !define MUI_ABORTWARNING ;Language Selection Dialog Settings ;Remember the installer language !define MUI_LANGDLL_REGISTRY_ROOT "HKCU" !define MUI_LANGDLL_REGISTRY_KEY "Software\Modern UI Test" !define MUI_LANGDLL_REGISTRY_VALUENAME "Installer Language" ;Pages !define MUI_ICON "tryton\data\pixmaps\tryton\tryton.ico" !define MUI_LICENSEPAGE_TEXT_BOTTOM "$(LicenseText)" !define MUI_LICENSEPAGE_BUTTON "$(LicenseNext)" !insertmacro MUI_PAGE_LICENSE "LICENSE" !insertmacro MUI_PAGE_DIRECTORY !insertmacro MUI_PAGE_STARTMENU Application $STARTMENU_FOLDER !insertmacro MUI_PAGE_INSTFILES !insertmacro MUI_UNPAGE_CONFIRM !insertmacro MUI_UNPAGE_INSTFILES ;Languages !insertmacro MUI_LANGUAGE "English" ; First is the default !include "english.nsh" !insertmacro MUI_LANGUAGE "Catalan" !include "catalan.nsh" !insertmacro MUI_LANGUAGE "French" !include "french.nsh" !insertmacro MUI_LANGUAGE "German" !include "german.nsh" !insertmacro MUI_LANGUAGE "Farsi" !include "farsi.nsh" !insertmacro MUI_LANGUAGE "Slovenian" !include "slovenian.nsh" !insertmacro MUI_LANGUAGE "Spanish" !include "spanish.nsh" ;Reserve Files ;If you are using solid compression, files that are required before ;the actual installation should be stored first in the data block, ;because this will make your installer start faster. !insertmacro MUI_RESERVEFILE_LANGDLL ;Installer Sections Function .onInit ClearErrors ReadRegStr $0 HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\tryton-${BITS}-${SERIES}" "UninstallString" StrCmp $0 "" DoInstall MessageBox MB_OKCANCEL|MB_ICONEXCLAMATION "$(PreviousInstall)" /SD IDOK IDOK Uninstall Quit Uninstall: ReadRegStr $1 HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\tryton-${BITS}-${SERIES}" "InstallLocation" ClearErrors StrCpy $2 "/S" IfSilent +2 StrCpy $2 "" ExecWait '$0 $2 _?=$1' IfErrors 0 DoInstall Quit DoInstall: FunctionEnd Section $(SecTrytonName) SecTryton SectionIn 1 2 RO ;Set output path to the installation directory SetOutPath "$INSTDIR" ;Put file File /r "dist\*" File "COPYRIGHT" File "LICENSE" File "CHANGELOG" SetOutPath "$INSTDIR\doc" File /r "doc\*" ;Register URL protocol WriteRegStr HKCR "tryton" "" "URL:Tryton Protocol" WriteRegStr HKCR "tryton" "URL Protocol" "" WriteRegStr HKCR "tryton\DefaultIcon" "" "$INSTDIR\tryton.exe,1" WriteRegStr HKCR "tryton\shell\open\command" "" '$INSTDIR\tryton.exe "%1"' ;Write the installation path into the registry WriteRegStr HKLM "Software\Tryton-${BITS}-${SERIES}" "" $INSTDIR ;Write the uninstall keys for Windows WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\tryton-${BITS}-${SERIES}" "DisplayName" "Tryton ${BITS} ${VERSION} (remove only)" WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\tryton-${BITS}-${SERIES}" "DisplayIcon" "$INSTDIR\tryton.exe,1" WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\tryton-${BITS}-${SERIES}" "UninstallString" "$INSTDIR\uninstall.exe" WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\tryton-${BITS}-${SERIES}" "InstallLocation" "$INSTDIR" ;Create the uninstaller WriteUninstaller uninstall.exe SectionEnd Section $(SecStartMenuName) SecStartMenu SectionIn 1 2 !insertmacro MUI_STARTMENU_WRITE_BEGIN Application SetShellVarContext all CreateDirectory "$SMPROGRAMS\$STARTMENU_FOLDER" CreateShortCut "$SMPROGRAMS\$STARTMENU_FOLDER\Uninstall.lnk" "$INSTDIR\uninstall.exe" "" "$INSTDIR\uninstall.exe" 0 CreateShortCut "$SMPROGRAMS\$STARTMENU_FOLDER\Tryton-${BITS}-${SERIES}.lnk" "$INSTDIR\tryton.exe" "" "$INSTDIR\tryton.exe" 0 CreateShortCut "$DESKTOP\Tryton-${BITS}-${SERIES}.lnk" "$INSTDIR\tryton.exe" "" "$INSTDIR\tryton.exe" 0 !insertmacro MUI_STARTMENU_WRITE_END SectionEnd ;Descriptions !insertmacro MUI_FUNCTION_DESCRIPTION_BEGIN !insertmacro MUI_DESCRIPTION_TEXT ${SecTryton} $(SecTrytonDesc) !insertmacro MUI_DESCRIPTION_TEXT ${SecStartMenu} $(SecStartMenuDesc) !insertmacro MUI_FUNCTION_DESCRIPTION_END Section "Uninstall" ;Add your stuff here RMDIR /r "$INSTDIR" ;remove registry keys DeleteRegKey HKLM "Software\Tryton-${BITS}-${SERIES}" DeleteRegKey HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\tryton-${BITS}-${SERIES}" SetShellVarContext all Delete "$DESKTOP\Tryton-${BITS}-${SERIES}.lnk" !insertmacro MUI_STARTMENU_GETFOLDER Application $MUI_TEMP StrCmp $MUI_TEMP "" noshortcuts Delete "$SMPROGRAMS\$MUI_TEMP\Uninstall.lnk" Delete "$SMPROGRAMS\$MUI_TEMP\Tryton-${BITS}-${SERIES}.lnk" RMDir "$SMPROGRAMS\$MUI_TEMP" noshortcuts: SectionEnd ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1743204724.0 tryton-7.0.24/setup.py0000755000175000017500000001057514771630564013015 0ustar00cedced#!/usr/bin/env python3 # This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. import io import os import re from setuptools import find_packages, setup def read(fname): return io.open( os.path.join(os.path.dirname(__file__), fname), 'r', encoding='utf-8').read() args = {} try: from babel.messages import frontend as babel class extract_messages(babel.extract_messages): def initialize_options(self): super().initialize_options() self.omit_header = True self.no_location = True class update_catalog(babel.update_catalog): def initialize_options(self): super().initialize_options() self.omit_header = True self.ignore_obsolete = True args['cmdclass'] = { 'compile_catalog': babel.compile_catalog, 'extract_messages': extract_messages, 'init_catalog': babel.init_catalog, 'update_catalog': update_catalog, } args['message_extractors'] = { 'tryton': [ ('**.py', 'python', None), ], } except ImportError: pass package_data = { 'tryton': ['data/pixmaps/tryton/*.png', 'data/pixmaps/tryton/*.svg', 'data/sounds/*.wav', 'data/locale/*/LC_MESSAGES/*.mo', 'data/locale/*/LC_MESSAGES/*.po', ] } data_files = [] def get_version(): init = read(os.path.join('tryton', '__init__.py')) return re.search('__version__ = "([0-9.]*)"', init).group(1) version = get_version() major_version, minor_version, _ = version.split('.', 2) major_version = int(major_version) minor_version = int(minor_version) name = 'tryton' if minor_version % 2: download_url = '' else: download_url = 'http://downloads.tryton.org/%s.%s/' % ( major_version, minor_version) dist = setup(name=name, version=version, description='Tryton desktop client', long_description=read('README.rst'), author='Tryton', author_email='foundation@tryton.org', url='http://www.tryton.org/', download_url=download_url, project_urls={ "Bug Tracker": 'https://bugs.tryton.org/', "Documentation": 'https://docs.tryton.org/latest/client-desktop/', "Forum": 'https://www.tryton.org/forum', "Source Code": 'https://code.tryton.org/tryton', }, keywords='business application ERP', packages=find_packages(), package_data=package_data, data_files=data_files, scripts=['bin/tryton'], classifiers=[ 'Development Status :: 5 - Production/Stable', 'Environment :: X11 Applications :: GTK', 'Framework :: Tryton', 'Intended Audience :: End Users/Desktop', 'License :: OSI Approved :: GNU General Public License (GPL)', 'Natural Language :: Bulgarian', 'Natural Language :: Catalan', 'Natural Language :: Chinese (Simplified)', 'Natural Language :: Czech', 'Natural Language :: Dutch', 'Natural Language :: English', 'Natural Language :: Finnish', 'Natural Language :: French', 'Natural Language :: German', 'Natural Language :: Hungarian', 'Natural Language :: Indonesian', 'Natural Language :: Italian', 'Natural Language :: Persian', 'Natural Language :: Polish', 'Natural Language :: Portuguese (Brazilian)', 'Natural Language :: Romanian', 'Natural Language :: Russian', 'Natural Language :: Slovenian', 'Natural Language :: Spanish', 'Natural Language :: Turkish', 'Natural Language :: Japanese', 'Natural Language :: Ukrainian', 'Operating System :: OS Independent', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3.9', 'Programming Language :: Python :: 3.10', 'Programming Language :: Python :: 3.11', 'Programming Language :: Python :: 3.12', 'Topic :: Office/Business', ], platforms='any', license='GPL-3', python_requires='>=3.8', install_requires=[ 'pycairo', "python-dateutil", 'PyGObject>=3.19,<3.51', ], extras_require={ 'calendar': ['GooCalendar>=0.7'], 'sound': ['playsound'], }, zip_safe=False, **args ) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685598.0 tryton-7.0.24/slovenian.nsh0000644000175000017500000000201714517761236013777 0ustar00cedced;This file is part of Tryton. The COPYRIGHT file at the top level of ;this repository contains the full copyright notices and license terms. !verbose 3 !ifdef CURLANG !undef CURLANG !endif !define CURLANG ${LANG_SLOVENIAN} LangString LicenseText ${CURLANG} "Tryton je izdan pod licenco GNU General Public License, kot jo je objavila Free Software Foundation, bodisi pod razlièico 3 ali (po va¹i izbiri) katerokoli poznej¹o razlièico. Licenco pozorno preberite. Kliknite Naprej za nadaljevanje." LangString LicenseNext ${CURLANG} "&Naprej" LangString PreviousInstall ${CURLANG} "Tryton je že nameščen.$\n$\nKliknite »V redu«, da odstranite prejšnjo različico ali »Prekliči«, da prekličete to nadgradnjo." LangString SecTrytonName ${CURLANG} "Tryton" LangString SecTrytonDesc ${CURLANG} "Namestitev tryton.exe in ostalih potrebnih datotek" LangString SecStartMenuName ${CURLANG} "Bli¾njici v zaèetnem meniju in na namizju" LangString SecStartMenuDesc ${CURLANG} "Ustvari bli¾njici v zaèetnem meniju in na namizju" ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685599.0 tryton-7.0.24/spanish.nsh0000644000175000017500000000210714517761237013447 0ustar00cedced;This file is part of Tryton. The COPYRIGHT file at the top level of ;this repository contains the full copyright notices and license terms. !verbose 3 !ifdef CURLANG !undef CURLANG !endif !define CURLANG ${LANG_SPANISH} LangString LicenseText ${CURLANG} "Tryton está liberado bajo la licencia «GNU General Public License» publicada por la Free Software Foundation, o bien la versión 3 de la licencia, o (a su elección) cualquier versión posterior. Lea cuidadosamente la licencia. Pulse «Siguiente» para continuar." LangString LicenseNext ${CURLANG} "&Siguiente" LangString PreviousInstall ${CURLANG} "Tryton ya está instalado.$\n$\nPulse `OK` para eliminar la versión anterior o `Cancelar` para cancelar la actualización." LangString SecTrytonName ${CURLANG} "Tryton" LangString SecTrytonDesc ${CURLANG} "Instalar tryton.exe y otros archivos necesarios" LangString SecStartMenuName ${CURLANG} "Accesos directos en el menú de inicio y en el escritorio" LangString SecStartMenuDesc ${CURLANG} "Crear accesos directos en el menú de inicio y en el escritorio" ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685599.0 tryton-7.0.24/tox.ini0000644000175000017500000000042614517761237012605 0ustar00cedced[tox] envlist = py38,py39,py310,py311,py312 [testenv] usedevelop = true commands = coverage run --omit=*/tests/* -m xmlrunner discover -s tryton.tests {posargs} commands_post = coverage report coverage xml deps = coverage unittest-xml-reporting passenv = * ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1745680285.1441104 tryton-7.0.24/tryton/0000755000175000017500000000000015003173635012615 5ustar00cedced././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1743633698.0 tryton-7.0.24/tryton/__init__.py0000644000175000017500000000360714773336442014746 0ustar00cedced# This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. __version__ = "7.0.24" import locale import gi gi.require_version('Gtk', '3.0') gi.require_version('Gdk', '3.0') gi.require_foreign('cairo') try: gi.require_version('GtkSpell', '3.0') except ValueError: pass try: gi.require_version('EvinceDocument', '3.0') gi.require_version('EvinceView', '3.0') except ValueError: pass try: # Import earlier otherwise there is a segmentation fault on MSYS2 import goocalendar # noqa: F401 except ImportError: pass if not hasattr(locale, 'localize'): def localize(formatted, grouping=False, monetary=False): if '.' in formatted: seps = 0 parts = formatted.split('.') if grouping: parts[0], seps = locale._group(parts[0], monetary=monetary) decimal_point = locale.localeconv()[ monetary and 'mon_decimal_point' or 'decimal_point'] formatted = decimal_point.join(parts) if seps: formatted = locale._strip_padding(formatted, seps) else: seps = 0 if grouping: formatted, seps = locale._group(formatted, monetary=monetary) if seps: formatted = locale._strip_padding(formatted, seps) return formatted setattr(locale, 'localize', localize) def delocalize(string, monetary=False): conv = locale.localeconv() # First, get rid of the grouping ts = conv[monetary and 'mon_thousands_sep' or 'thousands_sep'] if ts: string = string.replace(ts, '') # next, replace the decimal point with a dot dd = conv[monetary and 'mon_decimal_point' or 'decimal_point'] if dd: string = string.replace(dd, '.') return string setattr(locale, 'delocalize', delocalize) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1745680285.1474438 tryton-7.0.24/tryton/action/0000755000175000017500000000000015003173635014072 5ustar00cedced././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685599.0 tryton-7.0.24/tryton/action/__init__.py0000644000175000017500000000027514517761237016221 0ustar00cedced# This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. from .main import Action __all__ = [Action] ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685599.0 tryton-7.0.24/tryton/action/main.py0000644000175000017500000001514614517761237015411 0ustar00cedced# This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. import gettext import webbrowser import tryton.rpc as rpc from tryton.common import ( RPCException, RPCExecute, file_open, file_write, message, selection) from tryton.pyson import PYSONDecoder _ = gettext.gettext class Action(object): @staticmethod def exec_report(name, data, direct_print=False, context=None): if context is None: context = {} else: context = context.copy() context['direct_print'] = direct_print ids = data.get('ids', []) def callback(result): try: result = result() except RPCException: return type, data, print_p, name = result if not print_p and direct_print: print_p = True fp_name = file_write((name, type), data) file_open(fp_name, type, print_p=print_p) RPCExecute( 'report', name, 'execute', ids, data, context=context, callback=callback) @staticmethod def execute(action, data, context=None, keyword=False): if isinstance(action, int): # Must be executed synchronously to avoid double execution # on double click. action = RPCExecute( 'model', 'ir.action', 'get_action_value', action, context=context) if keyword: keywords = { 'ir.action.report': 'form_report', 'ir.action.wizard': 'form_action', 'ir.action.act_window': 'form_relate', } action.setdefault('keyword', keywords.get(action['type'], '')) Action._exec_action(action, data, context=context) @staticmethod def _exec_action(action, data=None, context=None): from tryton.gui.window import Window if context is None: context = {} else: context = context.copy() if data is None: data = {} else: data = data.copy() if 'type' not in (action or {}): return context.pop('active_id', None) context.pop('active_ids', None) context.pop('active_model', None) def add_name_suffix(name, context=None): if not data.get('ids') or not data.get('model'): return name max_records = 5 ids = list(filter(lambda id: id >= 0, data['ids']))[:max_records] if not ids: return name rec_names = RPCExecute('model', data['model'], 'read', ids, ['rec_name'], context=context) name_suffix = _(', ').join([x['rec_name'] for x in rec_names]) if len(data['ids']) > len(ids): name_suffix += _(',...') if name_suffix: return _('%s (%s)') % (name, name_suffix) else: return name data['action_id'] = action['id'] params = { 'icon': action.get('icon.rec_name') or '', } if action['type'] == 'ir.action.act_window': if action.get('views', []): params['view_ids'] = [x[0] for x in action['views']] params['mode'] = [x[1] for x in action['views']] elif action.get('view_id', False): params['view_ids'] = [action['view_id'][0]] action.setdefault('pyson_domain', '[]') ctx = { 'active_model': data.get('model'), 'active_id': data.get('id'), 'active_ids': data.get('ids') or [], } ctx.update(rpc.CONTEXT) ctx['_user'] = rpc._USER decoder = PYSONDecoder(ctx) params['context'] = context.copy() params['context'].update( decoder.decode(action.get('pyson_context') or '{}')) ctx.update(params['context']) ctx['context'] = ctx decoder = PYSONDecoder(ctx) params['domain'] = decoder.decode(action['pyson_domain']) params['order'] = decoder.decode(action['pyson_order']) params['search_value'] = decoder.decode( action['pyson_search_value'] or '[]') params['tab_domain'] = [ (n, decoder.decode(d), c) for n, d, c in action['domains']] name = action.get('name', '') if action.get('keyword', ''): name = add_name_suffix(name, params['context']) params['name'] = name res_model = action.get('res_model', data.get('res_model')) params['res_id'] = action.get('res_id', data.get('res_id')) params['context_model'] = action.get('context_model') params['context_domain'] = action.get('context_domain') limit = action.get('limit') if limit is not None: params['limit'] = limit Window.create(res_model, **params) elif action['type'] == 'ir.action.wizard': params['context'] = context params['window'] = action.get('window') name = action.get('name', '') if action.get('keyword', 'form_action') == 'form_action': name = add_name_suffix(name, context) params['name'] = name Window.create_wizard(action['wiz_name'], data, **params) elif action['type'] == 'ir.action.report': params['direct_print'] = action.get('direct_print', False) params['context'] = context del params['icon'] Action.exec_report(action['report_name'], data, **params) elif action['type'] == 'ir.action.url': if action['url']: webbrowser.open(action['url'], new=2) @staticmethod def exec_keyword(keyword, data=None, context=None, warning=True, alwaysask=False): actions = [] model_id = data.get('id', False) try: actions = RPCExecute('model', 'ir.action.keyword', 'get_keyword', keyword, (data['model'], model_id)) except RPCException: return False keyact = {} for action in actions: keyact[action['name'].split(' / ')[-1]] = action res = selection(_('Select your action'), keyact, alwaysask=alwaysask) if res: (name, action) = res Action.execute(action, data, context=context) return (name, action) elif not len(keyact) and warning: message(_('No action defined.')) return False ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685599.0 tryton-7.0.24/tryton/bus.py0000644000175000017500000000547214517761237014002 0ustar00cedced# This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. import base64 import json import logging import socket import threading import time import uuid from urllib.error import HTTPError from urllib.request import Request, urlopen from gi.repository import GLib from tryton.config import CONFIG from tryton.jsonrpc import object_hook logger = logging.getLogger(__name__) ID = str(uuid.uuid4()) CHANNELS = [ 'client:%s' % ID, ] def listen(connection): if not CONFIG['thread']: return listener = threading.Thread( target=_listen, args=(connection,), daemon=True) listener.start() def _listen(connection): bus_timeout = CONFIG['client.bus_timeout'] session = connection.session authorization = base64.b64encode(session.encode('utf-8')) headers = { 'Content-Type': 'application/json', 'Authorization': b'Session ' + authorization, } wait = 1 last_message = None url = None while connection.session == session: if url is None: if connection.url is None: time.sleep(1) continue url = connection.url + '/bus' request = Request(url, data=json.dumps({ 'last_message': last_message, 'channels': CHANNELS, }).encode('utf-8'), headers=headers) logger.info('poll channels %s with last message %s', CHANNELS, last_message) try: response = urlopen(request, timeout=bus_timeout) wait = 1 except socket.timeout: wait = 1 continue except Exception as error: if isinstance(error, HTTPError): if error.code in (301, 302, 303, 307, 308): url = error.headers.get('Location') continue elif error.code == 501: logger.info("Bus not supported") break logger.error( "An exception occurred while connecting to the bus. " "Sleeping for %s seconds", wait, exc_info=error) time.sleep(min(wait, bus_timeout)) wait *= 2 continue if connection.session != session: break data = json.loads(response.read(), object_hook=object_hook) if data['message']: last_message = data['message']['message_id'] GLib.idle_add(handle, data['message']) def handle(message): from tryton.gui.main import Main app = Main() if message['type'] == 'notification': app.show_notification( message.get('title', ''), message.get('body', ''), message.get('priority', 1)) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685598.0 tryton-7.0.24/tryton/client.py0000644000175000017500000000637314517761236014467 0ustar00cedced# This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. import hashlib import os import sys import traceback from urllib.parse import urlparse from gi.repository import Gdk, Gio, Gtk from tryton import __version__, common, gui, translate from tryton.config import CONFIG, copy_previous_configuration, get_config_dir from tryton.gui.window.dblogin import DBLogin def main(): CSS = b""" .readonly entry, .readonly text { background-color: @insensitive_bg_color; } .required entry, entry.required, .required text, text.required { border-color: darker(@unfocused_borders); } .invalid entry, entry.invalid, .invalid text, text.invalid { border-color: @error_color; } label.required { font-weight: bold; } label.editable { font-style: italic; } label.warning { color: @warning_color; } .window-title, .wizard-title { font-size: large; font-weight: bold; } .window-title .status { font-size: medium; font-weight: normal; } """ screen = Gdk.Screen.get_default() style_context = Gtk.StyleContext() provider = Gtk.CssProvider() provider.load_from_data(CSS) style_context.add_provider_for_screen( screen, provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION) theme_path = os.path.join(get_config_dir(), 'theme.css') if os.path.exists(theme_path): provider = Gtk.CssProvider() provider.load_from_path(theme_path) style_context.add_provider_for_screen( screen, provider, Gtk.STYLE_PROVIDER_PRIORITY_USER) def excepthook(type_, value, traceback_): common.error(value, ''.join(traceback.format_tb(traceback_))) sys.excepthook = excepthook copy_previous_configuration('tryton.cfg') copy_previous_configuration('profiles.cfg') copy_previous_configuration('plugins') CONFIG.parse() if CONFIG.arguments: url = CONFIG.arguments[0] urlp = urlparse(url) if urlp.scheme == 'tryton': urlp = urlparse('http' + url[6:]) database, _ = (urlp.path[1:].split('/', 1) + [None])[:2] CONFIG['login.host'] = urlp.netloc CONFIG['login.db'] = database CONFIG['login.expanded'] = True else: CONFIG.arguments = [] translate.set_language_direction(CONFIG['client.language_direction']) translate.setlang(CONFIG['client.lang']) if CONFIG.arguments or DBLogin().run(): server = '%(hostname)s:%(port)s/%(database)s' % { 'hostname': common.get_hostname(CONFIG['login.host']), 'port': common.get_port(CONFIG['login.host']), 'database': CONFIG['login.db'], } server = hashlib.md5(server.encode('utf-8')).hexdigest() version = ''.join(__version__.split('.')[:2]) application_id = 'org.tryton.Tryton-%(version)s._%(server)s' % { 'version': version, 'server': server, } app = gui.Main( application_id=application_id, flags=Gio.ApplicationFlags.HANDLES_COMMAND_LINE) app.run([sys.argv[0]] + CONFIG.arguments) if __name__ == "__main__": main() ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1745680285.1607773 tryton-7.0.24/tryton/common/0000755000175000017500000000000015003173635014105 5ustar00cedced././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685599.0 tryton-7.0.24/tryton/common/__init__.py0000644000175000017500000000461614517761237016237 0ustar00cedced# This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. from . import timedelta from .common import ( COLOR_SCHEMES, MODELACCESS, MODELHISTORY, MODELNAME, MODELNOTIFICATION, TRYTON_ICON, VIEW_SEARCH, IconFactory, Logout, RPCContextReload, RPCException, RPCExecute, RPCProgress, Tooltips, apply_label_attributes, ask, check_version, concurrency, data2pixbuf, date_format, ellipsize, error, file_open, file_selection, file_write, filter_domain, generateColorscheme, get_align, get_credentials, get_gdk_backend, get_hostname, get_port, get_sensible_widget, get_toplevel_window, hex2rgb, highlight_rgb, humanize, idle_add, mailto, message, node_attributes, open_documentation, play_sound, process_exception, resize_pixbuf, selection, setup_window, slugify, sur, sur_3b, timezoned_date, to_xml, untimezoned_date, url_open, userwarning, warning) from .domain_inversion import ( concat, domain_inversion, eval_domain, extract_reference_models, filter_leaf, inverse_leaf, localize_domain, merge, prepare_reference_domain, simplify, unique_value) from .environment import EvalEnvironment __all__ = [ COLOR_SCHEMES, EvalEnvironment, IconFactory, Logout, MODELACCESS, MODELHISTORY, MODELNAME, MODELNOTIFICATION, RPCContextReload, RPCException, RPCExecute, RPCProgress, TRYTON_ICON, Tooltips, VIEW_SEARCH, apply_label_attributes, ask, check_version, concat, simplify, concurrency, data2pixbuf, date_format, domain_inversion, ellipsize, error, eval_domain, extract_reference_models, file_open, file_selection, file_write, filter_domain, filter_leaf, generateColorscheme, get_align, get_credentials, get_gdk_backend, get_hostname, get_port, get_sensible_widget, get_toplevel_window, hex2rgb, highlight_rgb, humanize, idle_add, inverse_leaf, localize_domain, mailto, merge, message, node_attributes, open_documentation, play_sound, prepare_reference_domain, process_exception, resize_pixbuf, selection, setup_window, slugify, sur, sur_3b, timedelta, timezoned_date, to_xml, unique_value, untimezoned_date, url_open, userwarning, warning, ] ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1702244103.0 tryton-7.0.24/tryton/common/button.py0000644000175000017500000000400014535427407015774 0ustar00cedced# This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. import gettext from gi.repository import Gtk from tryton.common import IconFactory _ = gettext.gettext class Button(Gtk.Button): def __init__(self, attrs=None): self.attrs = attrs or {} self.label = '_' + attrs.get('string', '').replace('_', '__') super(Button, self).__init__(label=self.label, stock=None, use_underline=True) self._set_icon(attrs.get('icon')) def _set_icon(self, stock): self.set_always_show_image(bool(stock)) image = self.get_image() if not image and not stock: return if not stock: self.set_image(None) return self.set_image(IconFactory.get_image(stock, Gtk.IconSize.BUTTON)) def state_set(self, record): if record: states = record.expr_eval(self.attrs.get('states', {})) else: states = {} if states.get('invisible', False): self.hide() else: self.show() self.set_sensitive(not states.get('readonly', False)) self._set_icon(states.get('icon', self.attrs.get('icon'))) if self.attrs.get('rule'): label = self.label tip = self.attrs.get('help', '') if record: clicks = record.get_button_clicks(self.attrs['name']) if clicks: label += ' (%s)' % len(clicks) if tip: tip += '\n' tip += _('By: ') + _(', ').join(iter(clicks.values())) self.set_label(label) self.set_tooltip_text(tip) if self.attrs.get('type', 'class') == 'class': parent = record.parent if record else None while parent: if parent.modified: self.set_sensitive(False) break parent = parent.parent ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685599.0 tryton-7.0.24/tryton/common/cellrendererbinary.py0000644000175000017500000001472314517761237020353 0ustar00cedced# This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. from gi.repository import Gdk, GObject, Gtk, Pango from .common import IconFactory BUTTON_BORDER = 2 BUTTON_SPACING = 1 class CellRendererBinary(Gtk.CellRenderer): __gproperties__ = { 'visible': (GObject.TYPE_BOOLEAN, 'Visible', 'Visible', True, GObject.ParamFlags.READWRITE), 'editable': (GObject.TYPE_BOOLEAN, 'Editable', 'Editable', False, GObject.ParamFlags.READWRITE), 'size': (GObject.TYPE_STRING, 'Size', 'Size', '', GObject.ParamFlags.READWRITE), } __gsignals__ = { 'select': (GObject.SignalFlags.RUN_LAST, GObject.TYPE_NONE, (GObject.TYPE_STRING,)), 'open': (GObject.SignalFlags.RUN_LAST, GObject.TYPE_NONE, (GObject.TYPE_STRING,)), 'save': (GObject.SignalFlags.RUN_LAST, GObject.TYPE_NONE, (GObject.TYPE_STRING,)), 'clear': (GObject.SignalFlags.RUN_LAST, GObject.TYPE_NONE, (GObject.TYPE_STRING,)), } def __init__(self, use_filename): Gtk.CellRenderer.__init__(self) self.visible = True self.editable = False self.set_property('mode', Gtk.CellRendererMode.ACTIVATABLE) self.use_filename = use_filename self.images = {} for key, icon in ( ('select', 'tryton-search'), ('open', 'tryton-open'), ('save', 'tryton-download'), ('clear', 'tryton-clear')): img_sensitive = IconFactory.get_pixbuf( icon, Gtk.IconSize.SMALL_TOOLBAR) img_insensitive = img_sensitive.copy() img_sensitive.saturate_and_pixelate(img_insensitive, 0, False) width = img_sensitive.get_width() height = img_sensitive.get_height() self.images[key] = (img_sensitive, img_insensitive, width, height) @property def buttons(self): buttons = [] if self.size: if self.use_filename: buttons.append('open') buttons.append('save') buttons.append('clear') else: buttons.append('select') return buttons def do_set_property(self, pspec, value): setattr(self, pspec.name, value) def do_get_property(self, pspec): return getattr(self, pspec.name) def button_width(self): return (sum(width for n, (_, _, width, _) in self.images.items() if n in self.buttons) + (2 * (BUTTON_BORDER + BUTTON_SPACING) * len(self.buttons)) - 2 * BUTTON_SPACING) def do_get_size(self, widget, cell_area=None): if cell_area is None: return (0, 0, 30, 18) else: return (cell_area.x, cell_area.y, cell_area.width, cell_area.height) def do_activate( self, event, widget, path, background_area, cell_area, flags): if event is None: return button_width = self.button_width() for index, button_name in enumerate(self.buttons): _, _, pxbf_width, _ = self.images[button_name] if index == 0 and button_name == 'open': x_offset = 0 else: x_offset = (cell_area.width - button_width + (pxbf_width + (2 * BUTTON_BORDER) + BUTTON_SPACING) * index) x_button = cell_area.x + x_offset if x_button < event.x < (x_button + pxbf_width + (2 * BUTTON_BORDER)): break else: button_name = None if not self.visible or not button_name: return elif not self.editable and button_name in ('select', 'clear'): return elif not self.size and button_name in {'open', 'save'}: return self.emit(button_name, path) return True def do_render(self, cr, widget, background_area, cell_area, flags): if not self.visible: return button_width = self.button_width() state = self.get_state(widget, flags) context = widget.get_style_context() context.save() context.add_class('button') xpad, ypad = self.get_padding() x = cell_area.x + xpad y = cell_area.y + ypad w = cell_area.width - 2 * xpad h = cell_area.height - 2 * ypad padding = context.get_padding(state) layout = widget.create_pango_layout(self.size) lwidth = w - button_width - padding.left - padding.right if lwidth < 0: lwidth = 0 layout.set_width(lwidth * Pango.SCALE) layout.set_ellipsize(Pango.EllipsizeMode.END) layout.set_wrap(Pango.WrapMode.CHAR) layout.set_alignment(Pango.Alignment.RIGHT) if lwidth > 0: lw, lh = layout.get_size() # Can not use get_pixel_extents lw /= Pango.SCALE lh /= Pango.SCALE lx = x + padding.left if self.buttons and self.buttons[0] == 'open': pxbf_width = self.images['open'][2] lx += pxbf_width + 2 * BUTTON_BORDER + BUTTON_SPACING ly = y + padding.top + 0.5 * ( h - padding.top - padding.bottom - lh) Gtk.render_layout(context, cr, lx, ly, layout) for index, button_name in enumerate(self.buttons): pxbf_sens, pxbf_insens, pxbf_width, pxbf_height = \ self.images[button_name] if (not self.editable and button_name in {'select', 'clear'} or not self.size and button_name in {'open', 'save'}): pixbuf = pxbf_insens else: pixbuf = pxbf_sens if index == 0 and button_name == 'open': x_offset = 0 else: x_offset = (w - button_width + (pxbf_width + (2 * BUTTON_BORDER) + BUTTON_SPACING) * index) if x_offset < 0: continue bx = cell_area.x + x_offset by = cell_area.y bw = pxbf_width + (2 * BUTTON_BORDER) Gtk.render_background(context, cr, bx, by, bw, h) Gtk.render_frame(context, cr, bx, by, bw, h) Gdk.cairo_set_source_pixbuf( cr, pixbuf, bx + BUTTON_BORDER, by + (h - pxbf_height) / 2) cr.paint() context.restore() GObject.type_register(CellRendererBinary) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685599.0 tryton-7.0.24/tryton/common/cellrendererbutton.py0000644000175000017500000000571714517761237020405 0ustar00cedced# This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. from gi.repository import GObject, Gtk, Pango class CellRendererButton(Gtk.CellRenderer): __gproperties__ = { "text": (GObject.TYPE_STRING, None, "Text", "Displayed text", GObject.ParamFlags.READWRITE), 'visible': (GObject.TYPE_INT, 'Visible', 'Visible', 0, 10, 0, GObject.ParamFlags.READWRITE), 'sensitive': (GObject.TYPE_INT, 'Sensitive', 'Sensitive', 0, 10, 0, GObject.ParamFlags.READWRITE), } __gsignals__ = { 'clicked': (GObject.SignalFlags.RUN_LAST, GObject.TYPE_NONE, (GObject.TYPE_STRING,)), } def __init__(self, text=""): Gtk.CellRenderer.__init__(self) self.text = text self.set_property('mode', Gtk.CellRendererMode.EDITABLE) self.visible = True self.sensitive = True def do_set_property(self, pspec, value): setattr(self, pspec.name, value) def do_get_property(self, pspec): return getattr(self, pspec.name) def do_render(self, cr, widget, background_area, cell_area, flags): if not self.visible: return if not self.sensitive: state = Gtk.StateFlags.INSENSITIVE else: state = Gtk.StateFlags.NORMAL context = widget.get_style_context() context.save() context.add_class('text-button') context.set_state(state) xpad, ypad = self.get_padding() x = cell_area.x + xpad y = cell_area.y + ypad w = cell_area.width - 2 * xpad h = cell_area.height - 2 * ypad Gtk.render_background(context, cr, x, y, w, h) Gtk.render_frame(context, cr, x, y, w, h) padding = context.get_padding(state) layout = widget.create_pango_layout(self.text) layout.set_width((w - padding.left - padding.right) * Pango.SCALE) layout.set_ellipsize(Pango.EllipsizeMode.END) layout.set_wrap(Pango.WrapMode.CHAR) lw, lh = layout.get_size() # Can not use get_pixel_extents lw /= Pango.SCALE lh /= Pango.SCALE if w < lw: x = x + padding.left else: x = x + padding.left + 0.5 * ( w - padding.left - padding.right - lw) y = y + padding.top + 0.5 * (h - padding.top - padding.bottom - lh) Gtk.render_layout(context, cr, x, y, layout) context.restore() def do_get_size(self, widget, cell_area=None): if cell_area is None: return (0, 0, 30, 18) else: return (cell_area.x, cell_area.y, cell_area.width, cell_area.height) def do_start_editing( self, event, widget, path, background_area, cell_area, flags): if not self.visible or not self.sensitive: return self.emit("clicked", path) GObject.type_register(CellRendererButton) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685599.0 tryton-7.0.24/tryton/common/cellrendererclickablepixbuf.py0000644000175000017500000000130214517761237022203 0ustar00cedced# This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. from gi.repository import GObject, Gtk class CellRendererClickablePixbuf(Gtk.CellRendererPixbuf): __gsignals__ = { 'clicked': (GObject.SignalFlags.RUN_LAST, GObject.TYPE_NONE, (GObject.TYPE_STRING, )), } def __init__(self): Gtk.CellRendererPixbuf.__init__(self) self.set_property('mode', Gtk.CellRendererMode.ACTIVATABLE) def do_activate( self, event, widget, path, background_area, cell_area, flags): self.emit('clicked', path) GObject.type_register(CellRendererClickablePixbuf) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685599.0 tryton-7.0.24/tryton/common/cellrenderercombo.py0000644000175000017500000000073114517761237020160 0ustar00cedced# This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. from gi.repository import GObject, Gtk from tryton.common.selection import selection_shortcuts class CellRendererCombo(Gtk.CellRendererCombo): def on_editing_started(self, editable, path): super().on_editing_started(editable, path) selection_shortcuts(editable) GObject.type_register(CellRendererCombo) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685599.0 tryton-7.0.24/tryton/common/cellrendererfloat.py0000644000175000017500000000445414517761237020174 0ustar00cedced# This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. import locale from gi.repository import Gdk, GObject from .cellrendererinteger import CellRendererInteger class CellRendererFloat(CellRendererInteger): def __init__(self): super(CellRendererFloat, self).__init__() self.digits = None self.monetary = False self.convert = float def on_editing_started(self, editable, path): super().on_editing_started(editable, path) editable.connect('key-press-event', self.key_press_event) @property def __decimal_point(self): return locale.localeconv()['decimal_point'] @property def __thousands_sep(self): return locale.localeconv()['thousands_sep'] def key_press_event(self, widget, event): for name in ('KP_Decimal', 'KP_Separator'): if event.keyval == Gdk.keyval_from_name(name): text = self.__decimal_point try: start_pos, end_pos = widget.get_selection_bounds() except ValueError: start_pos = widget.get_position() end_pos = None if self._can_insert_text(widget, text, start_pos, end_pos): buffer_ = widget.get_buffer() if end_pos: buffer_.delete_text(start_pos, end_pos - start_pos) buffer_.insert_text(start_pos, text, len(text)) widget.set_position(widget.get_position() + len(text)) return True def _can_insert_text(self, entry, new_text, start_pos, end_pos=None): value = entry.get_text() if end_pos is None: end_pos = start_pos new_value = value[:start_pos] + new_text + value[end_pos:] if new_value not in {'-', self.__decimal_point, self.__thousands_sep}: try: value = self.convert( locale.delocalize(new_value, self.monetary)) except ValueError: return False if (value and self.digits is not None and round(value, self.digits[1]) != value): return False return True GObject.type_register(CellRendererFloat) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685598.0 tryton-7.0.24/tryton/common/cellrendererinteger.py0000644000175000017500000000210614517761236020513 0ustar00cedced# This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. import locale from gi.repository import GObject from .cellrenderertext import CellRendererText class CellRendererInteger(CellRendererText): def on_editing_started(self, editable, path): super().on_editing_started(editable, path) editable.set_alignment(1.0) editable.connect('insert_text', self.sig_insert_text) def _can_insert_text(self, entry, new_text, position): value = entry.get_text() new_value = value[:position] + new_text + value[position:] if new_value != '-': try: locale.atoi(new_value) except ValueError: return False return True def sig_insert_text(self, entry, new_text, new_text_length, position): position = entry.get_position() if not self._can_insert_text(entry, new_text, position): entry.stop_emission_by_name('insert-text') GObject.type_register(CellRendererInteger) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685599.0 tryton-7.0.24/tryton/common/cellrenderertext.py0000644000175000017500000000156114517761237020047 0ustar00cedced# This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. from gi.repository import GObject, Gtk class CellRendererText(Gtk.CellRendererText): def __init__(self): super(CellRendererText, self).__init__() self.connect('editing-started', self.__class__.on_editing_started) def on_editing_started(self, editable, path): pass class CellRendererTextCompletion(CellRendererText): def __init__(self, set_completion): super(CellRendererTextCompletion, self).__init__() self.set_completion = set_completion def on_editing_started(self, editable, path): super().on_editing_started(editable, path) self.set_completion(editable, path) GObject.type_register(CellRendererText) GObject.type_register(CellRendererTextCompletion) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685599.0 tryton-7.0.24/tryton/common/cellrenderertoggle.py0000644000175000017500000000044014517761237020337 0ustar00cedced# This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. from gi.repository import GObject, Gtk class CellRendererToggle(Gtk.CellRendererToggle): pass GObject.type_register(CellRendererToggle) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1726304377.0 tryton-7.0.24/tryton/common/common.py0000644000175000017500000013405314671250171015756 0ustar00cedced# This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. import colorsys import gettext import locale import logging import os import platform import re import subprocess import tempfile import unicodedata import xml.etree.ElementTree as ET from collections import defaultdict from decimal import Decimal from http.server import BaseHTTPRequestHandler, HTTPServer from pathlib import PurePath from urllib.parse import parse_qs, urlencode, urlparse, urlunparse try: from http import HTTPStatus except ImportError: from http import client as HTTPStatus import shlex import socket import sys import traceback import urllib.error import urllib.parse import urllib.request import webbrowser from functools import lru_cache, wraps from string import Template from threading import Lock, Thread import tryton.rpc as rpc from tryton.config import CONFIG, PIXMAPS_DIR, SOUNDS_DIR, TRYTON_ICON try: import ssl except ImportError: ssl = None import zipfile from gi.repository import Gdk, GdkPixbuf, Gio, GLib, GObject, Gtk from tryton import __version__ from tryton.exceptions import TrytonError, TrytonServerError from tryton.pyson import PYSONEncoder from .underline import set_underline from .widget_style import widget_class _ = gettext.gettext logger = logging.getLogger(__name__) class IconFactory: batchnum = 10 _tryton_icons = [] _name2id = {} _icons = {} _local_icons = {} _pixbufs = defaultdict(dict) @classmethod def load_local_icons(cls): for fname in os.listdir(PIXMAPS_DIR): name = os.path.splitext(fname)[0] path = os.path.join(PIXMAPS_DIR, fname) cls._local_icons[name] = path @classmethod def load_icons(cls, refresh=False): if not refresh: cls._name2id.clear() cls._icons.clear() del cls._tryton_icons[:] try: icons = rpc.execute('model', 'ir.ui.icon', 'list_icons', rpc.CONTEXT) except TrytonServerError: icons = [] for icon_id, icon_name in icons: if refresh and icon_name in cls._icons: continue cls._tryton_icons.append((icon_id, icon_name)) cls._name2id[icon_name] = icon_id @classmethod def register_icon(cls, iconname): # iconname might be '' when page do not define icon if (not iconname or iconname in cls._icons or iconname in cls._local_icons): return if iconname not in cls._name2id: cls.load_icons(refresh=True) try: icon_ref = (cls._name2id[iconname], iconname) except KeyError: logger.error(f"Unknown icon {iconname}") cls._icons[iconname] = None return idx = cls._tryton_icons.index(icon_ref) to_load = slice(max(0, idx - cls.batchnum // 2), idx + cls.batchnum // 2) ids = [e[0] for e in cls._tryton_icons[to_load]] try: icons = rpc.execute('model', 'ir.ui.icon', 'read', ids, ['name', 'icon'], rpc.CONTEXT) except TrytonServerError: icons = [] for icon in icons: name = icon['name'] data = icon['icon'].encode('utf-8') cls._icons[name] = data cls._tryton_icons.remove((icon['id'], icon['name'])) del cls._name2id[icon['name']] @classmethod def get_pixbuf(cls, iconname, size=16, color=None, badge=None): if not iconname: return colors = CONFIG['icon.colors'].split(',') cls.register_icon(iconname) if iconname not in cls._pixbufs[(size, badge)]: data = None if iconname in cls._icons: data = cls._icons[iconname] elif iconname in cls._local_icons: path = cls._local_icons[iconname] with open(path, 'rb') as fp: data = fp.read() if not data: logger.error("Unknown icon %s" % iconname) return if not color: color = colors[0] try: ET.register_namespace('', 'http://www.w3.org/2000/svg') root = ET.fromstring(data) root.attrib['fill'] = color if badge: if not isinstance(badge, str): try: badge = colors[badge] except IndexError: badge = color ET.SubElement(root, 'circle', { 'cx': '20', 'cy': '4', 'r': '4', 'fill': badge, }) data = ET.tostring(root) except ET.ParseError: pass width = height = { Gtk.IconSize.MENU: 16, Gtk.IconSize.SMALL_TOOLBAR: 16, Gtk.IconSize.LARGE_TOOLBAR: 24, Gtk.IconSize.BUTTON: 16, Gtk.IconSize.DND: 12, Gtk.IconSize.DIALOG: 48, }.get(size, size) pixbuf = data2pixbuf(data, width, height) cls._pixbufs[(size, badge)][iconname] = pixbuf return cls._pixbufs[(size, badge)][iconname] @classmethod def get_image(cls, iconname, size=16, color=None, badge=None): image = Gtk.Image() if iconname: pixbuf = cls.get_pixbuf(iconname, size, color, badge) image.set_from_pixbuf(pixbuf) return image @classmethod def _convert_url(cls, value, size=16, size_param=None): if not value: return parts = urllib.parse.urlsplit(value) parts = list(parts) if not parts[0]: parts[0] = 'https' if rpc.CONNECTION.ssl else 'http' if not parts[1]: hostname = get_hostname(CONFIG['login.host']) port = get_port(CONFIG['login.host']) parts[1] = '%s:%s' % (hostname, port) if size_param: query = urllib.parse.parse_qsl(parts[4]) query.append((size_param, size)) parts[4] = urllib.parse.urlencode(query) return urllib.parse.urlunsplit(parts) @classmethod @lru_cache(maxsize=CONFIG['image.cache_size']) def get_pixbuf_url(cls, url, size=16, size_param=None): if not url: return url = cls._convert_url(url, size, size_param=size_param) try: with urllib.request.urlopen(url) as response: return data2pixbuf(response.read(), size, size) except urllib.error.URLError: logger.info("Can not fetch %s", url, exc_info=True) IconFactory.load_local_icons() class ModelAccess(object): batchnum = 100 _access = {} _models = [] def load_models(self, refresh=False): if not refresh: self._access.clear() del self._models[:] try: self._models = rpc.execute('model', 'ir.model', 'list_models', rpc.CONTEXT) except TrytonServerError: logger.error("Unable to get model list.") def __getitem__(self, model): if model in self._access: return self._access[model] if model not in self._models: self.load_models(refresh=True) idx = self._models.index(model) to_load = slice(max(0, idx - self.batchnum // 2), idx + self.batchnum // 2) try: access = rpc.execute('model', 'ir.model.access', 'get_access', self._models[to_load], rpc.CONTEXT) except TrytonServerError: logger.error("Unable to get access for %s.", model) access = { model: { 'read': True, 'write': False, 'create': False, 'delete': False}, } self._access.update(access) return self._access[model] MODELACCESS = ModelAccess() class ModelHistory(object): _models = set() def load_history(self): self._models.clear() try: self._models.update(rpc.execute('model', 'ir.model', 'list_history', rpc.CONTEXT)) except TrytonServerError: pass def __contains__(self, model): return model in self._models MODELHISTORY = ModelHistory() class ModelName: _names = {} def load_names(self): try: self._names = rpc.execute( 'model', 'ir.model', 'get_names', rpc.CONTEXT) except TrytonServerError: pass def get(self, model): if not self._names: self.load_names() return self._names.get(model, '') def clear(self): return self._names.clear() MODELNAME = ModelName() class ModelNotification: _depends = None def load_names(self): try: self._depends = rpc.execute( 'model', 'ir.model', 'get_notification', rpc.CONTEXT) except TrytonServerError: pass def get(self, model): if self._depends is None: self.load_names() return self._depends.get(model, []) MODELNOTIFICATION = ModelNotification() class ViewSearch(object): searches = {} def __init__(self): class Encoder(PYSONEncoder): def default(self, obj): if isinstance(obj, Decimal): return float(obj) return super(Encoder, self).default(obj) self.encoder = Encoder() def load_searches(self): try: self.searches = RPCExecute('model', 'ir.ui.view_search', 'get') except RPCException: self.searches = {} def __getitem__(self, model): return self.searches.get(model, []) def add(self, model, name, domain): try: id_ = RPCExecute( 'model', 'ir.ui.view_search', 'set', name, model, self.encoder.encode(domain)) except RPCException: return self.searches.setdefault(model, []).append((id_, name, domain, True)) def remove(self, model, id_): try: RPCExecute('model', 'ir.ui.view_search', 'unset', id_) except RPCException: return for i, domain in enumerate(self.searches[model]): if domain[0] == id_: del self.searches[model][i] break VIEW_SEARCH = ViewSearch() def get_toplevel_window(): from tryton.gui.main import Main return Main().get_active_window() def get_sensible_widget(window): from tryton.gui.main import Main main = Main() if main and window == main.window: focus_widget = window.get_focus() page = main.get_page() if page and focus_widget and focus_widget.is_ancestor(page.widget): return page.widget return window def selection(title, values, alwaysask=False): if not values or len(values) == 0: return None elif len(values) == 1 and (not alwaysask): key = list(values.keys())[0] return (key, values[key]) parent = get_toplevel_window() dialog = Gtk.Dialog( title=_('Selection'), transient_for=parent, modal=True, destroy_with_parent=True) dialog.add_button(set_underline(_("Cancel")), Gtk.ResponseType.CANCEL) dialog.add_button(set_underline(_("OK")), Gtk.ResponseType.OK) dialog.set_icon(TRYTON_ICON) dialog.set_default_response(Gtk.ResponseType.OK) dialog.set_default_size(400, 400) label = Gtk.Label(title or _('Your selection:')) dialog.vbox.pack_start(label, expand=False, fill=False, padding=0) dialog.vbox.pack_start( Gtk.HSeparator(), expand=False, fill=True, padding=0) scrolledwindow = Gtk.ScrolledWindow() scrolledwindow.set_policy( Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC) treeview = Gtk.TreeView() treeview.set_headers_visible(False) scrolledwindow.add(treeview) dialog.vbox.pack_start(scrolledwindow, expand=True, fill=True, padding=0) treeview.get_selection().set_mode(Gtk.SelectionMode.SINGLE) cell = Gtk.CellRendererText() column = Gtk.TreeViewColumn("Widget", cell, text=0) treeview.append_column(column) treeview.set_search_column(0) model = Gtk.ListStore(GObject.TYPE_STRING, GObject.TYPE_INT) keys = list(values.keys()) keys.sort() i = 0 for val in keys: model.append([str(val), i]) i += 1 treeview.set_model(model) treeview.connect('row-activated', lambda x, y, z: dialog.response(Gtk.ResponseType.OK) or True) dialog.show_all() response = dialog.run() res = None if response == Gtk.ResponseType.OK: sel = treeview.get_selection().get_selected() if sel: (model, i) = sel if i: index = model.get_value(i, 1) value = keys[index] res = (value, values[value]) parent.present() dialog.destroy() return res def file_selection(title, filename='', action=Gtk.FileChooserAction.OPEN, preview=True, multi=False, filters=None): parent = get_toplevel_window() win = Gtk.FileChooserNative( title=title, transient_for=parent, action=action) if filename: if action in (Gtk.FileChooserAction.SAVE, Gtk.FileChooserAction.CREATE_FOLDER): filename = _slugify_filename(filename) win.set_current_name(filename) else: win.set_filename(filename) if hasattr(win, 'set_do_overwrite_confirmation'): win.set_do_overwrite_confirmation(True) win.set_select_multiple(multi) if filters is not None: for filt in filters: win.add_filter(filt) def update_preview_cb(win, img): have_preview = False filename = win.get_preview_filename() if filename: try: pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size( filename, 128, 128) img.set_from_pixbuf(pixbuf) have_preview = True except (IOError, GLib.GError): pass win.set_preview_widget_active(have_preview) return if preview: img_preview = Gtk.Image() win.set_preview_widget(img_preview) win.connect('update-preview', update_preview_cb, img_preview) button = win.run() if button != Gtk.ResponseType.ACCEPT: result = None elif not multi: result = PurePath(win.get_filename()) else: result = [PurePath(f) for f in win.get_filenames()] parent.present() win.destroy() return result _slugify_strip_re = re.compile(r'[^\w\s-]') _slugify_hyphenate_re = re.compile(r'[-\s]+') def slugify(value): if not isinstance(value, str): value = str(value) value = unicodedata.normalize('NFKD', value) value = str(_slugify_strip_re.sub('', value).strip()) return _slugify_hyphenate_re.sub('-', value) def _slugify_filename(filename): if not isinstance(filename, str): name, ext = filename else: name, ext = os.path.splitext(filename) return ''.join([slugify(name), os.extsep, slugify(ext)]) def file_write(filename, data): if isinstance(data, str): data = data.encode('utf-8') dtemp = tempfile.mkdtemp(prefix='tryton_') filename = _slugify_filename(filename) filepath = os.path.join(dtemp, filename) with open(filepath, 'wb') as fp: fp.write(data) return filepath def file_open(filename, type=None, print_p=False): def save(): save_name = file_selection(_('Save As...'), action=Gtk.FileChooserAction.SAVE) if save_name: file_p = open(filename, 'rb') save_p = open(save_name, 'wb+') save_p.write(file_p.read()) save_p.close() file_p.close() if print_p and type == 'zip': with zipfile.ZipFile(filename, 'r') as zip_file: for name in zip_file.namelist(): ztype = os.path.splitext(name) with zip_file.open(name) as zfile: zfilename = file_write(name, zfile.read()) file_open(zfilename, type=ztype, print_p=True) return if hasattr(os, 'startfile'): operation = 'print' if print_p else 'open' try: os.startfile(os.path.normpath(filename), operation) except WindowsError: save() elif print_p: if type in {'odt', 'odp', 'ods', 'odg'}: try: subprocess.Popen(['soffice', '-p', filename]) except OSError: save() else: try: subprocess.Popen(['lpr', filename]) except OSError: try: subprocess.Popen(['lp', filename]) except OSError: save() elif sys.platform == 'darwin': try: subprocess.Popen(['/usr/bin/open', filename]) except OSError: save() else: uri = GLib.filename_to_uri(filename) try: Gio.AppInfo.launch_default_for_uri(uri) except GLib.Error: save() def webbrowser_open(url): try: Gio.AppInfo.launch_default_for_uri(url) except GLib.Error: webbrowser.open(url) def url_open(uri): try: return urllib.request.urlopen(uri) except urllib.error.URLError: if sys.platform == 'win32' and uri.startswith('file://'): # There are two ways that Windows UNC filenames can be represented: # file://server/folder/data.xml # file:////server/folder/data.xml if uri.startswith('file:////'): uri = uri[len('file://'):] else: uri = uri[len('file:')] return open(uri) else: raise def mailto(to=None, cc=None, subject=None, body=None, attachment=None): if CONFIG['client.email']: cmd = Template(CONFIG['client.email']).substitute( to=to or '', cc=cc or '', subject=subject or '', body=body or '', attachment=attachment or '', ) args = shlex.split(str(cmd)) subprocess.Popen(args) return if os.name != 'nt' and sys.platform != 'darwin': args = ['xdg-email', '--utf8'] if cc: args.extend(['--cc', cc]) if subject: args.extend(['--subject', subject]) if body: args.extend(['--body', body]) if attachment: args.extend(['--attach', attachment]) if to: args.append(to) try: subprocess.Popen(args) return except OSError: pass # http://www.faqs.org/rfcs/rfc2368.html url = "mailto:" if to: url += urllib.parse.quote(to.strip(), "@,") url += '?' if cc: url += "&cc=" + urllib.parse.quote(cc, "@,") if subject: url += "&subject=" + urllib.parse.quote(subject, "") if body: body = "\r\n".join(body.splitlines()) url += "&body=" + urllib.parse.quote(body, "") if attachment: url += "&attachment=" + urllib.parse.quote(attachment, "") webbrowser_open(url, new=1) class UniqueDialog(object): def __init__(self): self.running = False def build_dialog(self, *args): raise NotImplementedError def process_response(self, response): return response def __call__(self, *args, **kwargs): if self.running: return parent = kwargs.pop('parent', None) if not parent: parent = get_toplevel_window() dialog = self.build_dialog(parent, *args, **kwargs) dialog.set_icon(TRYTON_ICON) setup_window(dialog) self.running = True dialog.show_all() response = dialog.run() response = self.process_response(response) if parent: parent.present() dialog.destroy() self.running = False return response class MessageDialog(UniqueDialog): def build_dialog(self, parent, message, msg_type=Gtk.MessageType.INFO, buttons=Gtk.ButtonsType.OK, secondary=None): dialog = Gtk.MessageDialog( transient_for=parent, modal=True, destroy_with_parent=True, message_type=msg_type, buttons=buttons, text=message) if secondary: dialog.format_secondary_text(secondary) return dialog def __call__(self, message, *args, **kwargs): return super(MessageDialog, self).__call__(message, *args, **kwargs) message = MessageDialog() class WarningDialog(MessageDialog): def __call__(self, message, title, buttons=Gtk.ButtonsType.OK, **kwargs): return super().__call__( title, Gtk.MessageType.WARNING, buttons, message, **kwargs) warning = WarningDialog() class UserWarningDialog(WarningDialog): def build_dialog(self, *args, **kwargs): dialog = super().build_dialog(*args, **kwargs) label = Gtk.Label( label=_('Do you want to proceed?'), halign=Gtk.Align.FILL, valign=Gtk.Align.END) dialog.vbox.pack_start(label, expand=True, fill=True, padding=0) self.always = Gtk.CheckButton( label=_('Always ignore this warning.'), halign=Gtk.Align.START) dialog.vbox.pack_start(self.always, expand=True, fill=False, padding=0) return dialog def process_response(self, response): if response == Gtk.ResponseType.YES: if self.always.get_active(): return 'always' return 'ok' return 'cancel' def __call__(self, message, title): return super().__call__(message, title, Gtk.ButtonsType.YES_NO) userwarning = UserWarningDialog() class ConfirmationDialog(MessageDialog): def __call__(self, message, *args, **kwargs): return super().__call__( message, Gtk.MessageType.QUESTION, *args, **kwargs) class SurDialog(ConfirmationDialog): def __call__(self, message): response = super().__call__(message, buttons=Gtk.ButtonsType.YES_NO) return response == Gtk.ResponseType.YES sur = SurDialog() class Sur3BDialog(ConfirmationDialog): response_mapping = { Gtk.ResponseType.YES: 'ok', Gtk.ResponseType.NO: 'ko', Gtk.ResponseType.CANCEL: 'cancel' } def build_dialog(self, *args, **kwargs): dialog = super().build_dialog(*args, **kwargs) dialog.add_button(set_underline(_("Cancel")), Gtk.ResponseType.CANCEL) dialog.add_button(set_underline(_("No")), Gtk.ResponseType.NO) dialog.add_button(set_underline(_("Yes")), Gtk.ResponseType.YES) dialog.set_default_response(Gtk.ResponseType.YES) return dialog def __call__(self, message): response = super().__call__(message, buttons=Gtk.ButtonsType.NONE) return self.response_mapping.get(response, 'cancel') sur_3b = Sur3BDialog() class AskDialog(MessageDialog): def build_dialog(self, *args, **kwargs): visibility = kwargs.pop('visibility') dialog = super().build_dialog(*args, **kwargs) dialog.set_default_response(Gtk.ResponseType.OK) box = dialog.get_message_area() self.entry = Gtk.Entry() self.entry.set_activates_default(True) self.entry.set_visibility(visibility) box.pack_start(self.entry, False, False, 0) return dialog def process_response(self, response): if response == Gtk.ResponseType.OK: return self.entry.get_text() def __call__(self, question, visibility=True): return super().__call__( question, Gtk.MessageType.QUESTION, buttons=Gtk.ButtonsType.OK_CANCEL, visibility=visibility) ask = AskDialog() class ConcurrencyDialog(UniqueDialog): def build_dialog(self, parent): tooltips = Tooltips() dialog = Gtk.MessageDialog( transient_for=parent, modal=True, destroy_with_parent=True, message_type=Gtk.MessageType.QUESTION, buttons=Gtk.ButtonsType.NONE, text=_('Concurrency Exception')) dialog.format_secondary_text( _('This record has been modified while you were editing it.')) cancel_button = dialog.add_button( set_underline(_("Cancel")), Gtk.ResponseType.CANCEL) tooltips.set_tip(cancel_button, _('Cancel saving')) compare_button = dialog.add_button( set_underline(_("Compare")), Gtk.ResponseType.APPLY) tooltips.set_tip(compare_button, _('See the modified version')) write_button = dialog.add_button( set_underline(_("Write Anyway")), Gtk.ResponseType.OK) tooltips.set_tip(write_button, _('Save your current version')) dialog.set_default_response(Gtk.ResponseType.CANCEL) return dialog def __call__(self, model, id_, context): res = super(ConcurrencyDialog, self).__call__() if res == Gtk.ResponseType.OK: return True if res == Gtk.ResponseType.APPLY: from tryton.gui.window import Window name = RPCExecute( 'model', model, 'read', [id_], ['rec_name'], context=context)[0]['rec_name'] with Window(allow_similar=True): Window.create( model, res_id=id_, name=_("Compare: %s") % name, domain=[('id', '=', id_)], context=context, mode=['form']) return False concurrency = ConcurrencyDialog() class ErrorDialog(UniqueDialog): def build_dialog(self, parent, title, details): dialog = Gtk.MessageDialog( transient_for=parent, modal=True, destroy_with_parent=True, message_type=Gtk.MessageType.ERROR, buttons=Gtk.ButtonsType.NONE) dialog.set_default_size(600, 400) dialog.set_position(Gtk.WindowPosition.CENTER) dialog.add_button(set_underline(_("Close")), Gtk.ResponseType.CANCEL) dialog.set_default_response(Gtk.ResponseType.CANCEL) dialog.set_markup( '%s' % GLib.markup_escape_text(_('Application Error'))) dialog.format_secondary_markup( '%s' % GLib.markup_escape_text(title)) scrolledwindow = Gtk.ScrolledWindow() scrolledwindow.set_policy( Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC) scrolledwindow.set_shadow_type(Gtk.ShadowType.NONE) viewport = Gtk.Viewport() viewport.set_shadow_type(Gtk.ShadowType.NONE) textview = Gtk.TextView(editable=False, sensitive=True, monospace=True) buf = Gtk.TextBuffer() buf.set_text(details) textview.set_buffer(buf) viewport.add(textview) scrolledwindow.add(viewport) dialog.vbox.pack_start( scrolledwindow, expand=True, fill=True, padding=0) button_roundup = Gtk.LinkButton.new_with_label( CONFIG['bug.url'], _("Report Bug")) button_roundup.get_child().set_halign(Gtk.Align.START) button_roundup.connect('activate-link', lambda widget: webbrowser_open(CONFIG['bug.url'], new=2)) dialog.vbox.pack_start( button_roundup, expand=False, fill=False, padding=0) return dialog def __call__(self, title, details): if isinstance(title, Exception): title = "%s: %s" % (title.__class__.__name__, title) details += '\n' + title logger.error(details) return super(ErrorDialog, self).__call__(title, details) error = ErrorDialog() def check_version(box, version=__version__): def info_bar_response(info_bar, response, box, url): if response == Gtk.ResponseType.ACCEPT: webbrowser_open(url) box.remove(info_bar) class HeadRequest(urllib.request.Request): def get_method(self): return 'HEAD' version = version.split('.') series = '.'.join(version[:2]) version[2] = str(int(version[2]) + 1) version = '.'.join(version) filename = 'tryton-%s.tar.gz' % version if hasattr(sys, 'frozen'): if sys.platform == 'win32': bits = platform.architecture()[0] filename = 'tryton-%s-%s.exe' % (bits, version) elif sys.platform == 'darwin': filename = 'tryton-%s.dmg' % version url = list(urllib.parse.urlparse(CONFIG['download.url'])) url[2] = '/%s/%s' % (series, filename) url = urllib.parse.urlunparse(url) logger.info(_("Check URL: %s"), url) try: urllib.request.urlopen( HeadRequest(url), timeout=5, cafile=rpc._CA_CERTS) except (urllib.error.HTTPError, socket.timeout): return True except Exception: logger.error( _("Unable to check for new version."), exc_info=True) return True else: if check_version(box, version): info_bar = Gtk.InfoBar() info_bar.get_content_area().pack_start( Gtk.Label(label=_("A new version is available!")), expand=True, fill=True, padding=0) info_bar.set_show_close_button(True) info_bar.add_button(_("Download"), Gtk.ResponseType.ACCEPT) info_bar.connect('response', info_bar_response, box, url) box.pack_start(info_bar, expand=True, fill=True, padding=0) info_bar.show_all() return False def open_documentation(): version = __version__.split('.')[:2] if int(version[-1]) % 2: version = 'latest' else: version = '.'.join(version) webbrowser_open(CONFIG['doc.url'] % { 'lang': CONFIG['client.lang'], 'version': version, }) def to_xml(string): return string.replace('&', '&' ).replace('<', '<').replace('>', '>') PLOCK = Lock() def process_exception(exception, *args, **kwargs): from .domain_parser import DomainParser rpc_execute = kwargs.get('rpc_execute', rpc.execute) if isinstance(exception, TrytonServerError): if exception.faultCode == 'UserWarning': name, msg, description = exception.args res = userwarning(description, msg) if res in ('always', 'ok'): RPCExecute( 'model', 'res.user.warning', 'skip', name, (res == 'always'), process_exception=False) return rpc_execute(*args) elif exception.faultCode == 'UserError': msg, description, domain = exception.args if domain: domain, fields = domain domain_parser = DomainParser(fields) if domain_parser.stringable(domain): description += '\n' + domain_parser.string(domain) warning(description, msg) elif exception.faultCode == 'ConcurrencyException': if (len(args) >= 6 and args[0] == 'model' and args[2] in {'write', 'delete'} and len(args[3]) == 1): if concurrency(args[1], args[3][0], args[5]): if '_timestamp' in args[5]: del args[5]['_timestamp'] return rpc_execute(*args) else: message( _('Concurrency Exception'), msg_type=Gtk.MessageType.ERROR) elif exception.faultCode == str(int(HTTPStatus.UNAUTHORIZED)): from tryton.gui.main import Main if PLOCK.acquire(False): try: get_credentials(rpc._USER) except TrytonError as exception: if exception.faultCode != 'QueryCanceled': message( _("Could not get a session."), msg_type=Gtk.MessageType.ERROR) Main().on_quit() sys.exit() finally: PLOCK.release() if args: return rpc_execute(*args) elif exception.faultCode == str(int(HTTPStatus.TOO_MANY_REQUESTS)): message( _('Too many requests. Try again later.'), msg_type=Gtk.MessageType.ERROR) elif exception.faultCode == str(int(HTTPStatus.NOT_FOUND)): message(_("Not found."), msg_type=Gtk.MessageType.ERROR) else: error(exception, exception.faultString) else: error(exception, traceback.format_exc()) raise RPCException(exception) def get_credentials(user_id=None): if not CONFIG['login.service']: Login() else: url = CONFIG['login.service'] port = CONFIG['login.service.port'] next_ = 'http://localhost:%s/' % port url_parts = list(urlparse(url)) query = dict(parse_qs(url_parts[4])) query['next'] = next_ if user_id is not None: query['renew'] = user_id url_parts[4] = urlencode(query) url = urlunparse(url_parts) webbrowser_open(url) class RequestHandler(BaseHTTPRequestHandler): def do_GET(self): self.send_response(200) self.send_header('Content-Type', 'text/html') self.end_headers() try: rpc.set_service_session( parse_qs(urlparse(self.path).query)) except ValueError: pass else: self.server.session_set = True self.wfile.write(b"""\ Authentication Status

The authentication flow has completed.

""") def log_message(self, *args): pass server = HTTPServer(('localhost', port), RequestHandler) server.timeout = 5 * 60 server.session_set = False server.handle_request() if not server.session_set: raise TrytonError('SessionFailed') class Login(object): def __init__(self, func=rpc.login): parameters = {} while True: try: func(parameters) except TrytonServerError as exception: if exception.faultCode == str(int(HTTPStatus.UNAUTHORIZED)): parameters.clear() continue if (exception.faultCode == str(int(HTTPStatus.TOO_MANY_REQUESTS))): message( _('Too many requests. Try again later.'), msg_type=Gtk.MessageType.ERROR) continue elif (exception.faultCode == str(int(HTTPStatus.NOT_FOUND))): message(_("Not Found."), msg_type=Gtk.MessageType.ERROR) raise TrytonError('QueryCanceled') if exception.faultCode != 'LoginException': raise name, msg, type = exception.args value = getattr(self, 'get_%s' % type)(msg) if value is None: raise TrytonError('QueryCanceled') parameters[name] = value continue else: return @classmethod def get_char(cls, message): return ask(message) @classmethod def get_password(cls, message): return ask(message, visibility=False) class Logout: def __init__(self): try: rpc.logout() except TrytonServerError: pass def node_attributes(node): result = {} attrs = node.attributes if attrs is None: return {} for i in range(attrs.length): result[str(attrs.item(i).localName)] = str(attrs.item(i).nodeValue) return result def hex2rgb(hexstring, digits=2): """ Converts a hexstring color to a rgb tuple. Example: #ff0000 -> (1.0, 0.0, 0.0) digits is an integer number telling how many characters should be interpreted for each component in the hexstring. """ if isinstance(hexstring, (tuple, list)): return hexstring top = float(int(digits * 'f', 16)) r = int(hexstring[1:digits + 1], 16) g = int(hexstring[digits + 1:digits * 2 + 1], 16) b = int(hexstring[digits * 2 + 1:digits * 3 + 1], 16) return r / top, g / top, b / top def highlight_rgb(r, g, b, amount=0.1): h, s, v = colorsys.rgb_to_hsv(r, g, b) return colorsys.hsv_to_rgb(h, s, (v + amount) % 1) def generateColorscheme(masterColor, keys, light=0.1): """ Generates a dictionary where the keys match the keys argument and the values are colors derivated from the masterColor. Each color has a value higher then the previous of `light`. Each color has a hue separated from the previous by the golden angle. The masterColor is given in a hex string format. """ r, g, b = hex2rgb(COLOR_SCHEMES.get(masterColor, masterColor)) h, s, v = colorsys.rgb_to_hsv(r, g, b) if keys: light = min(light, (1. - v) / len(keys)) golden_angle = 0.618033988749895 return {key: colorsys.hsv_to_rgb((h + golden_angle * i) % 1, s, (v + light * i) % 1) for i, key in enumerate(keys)} class RPCException(Exception): def __init__(self, exception): super(RPCException, self).__init__(exception) self.exception = exception class RPCProgress(object): def __init__(self, method, args): self.method = method self.args = args self.parent = None self.res = None self.error = False self.exception = None self._cursor_timeout = None def start(self): try: self.res = getattr(rpc, self.method)(*self.args) except Exception as exception: self.error = True self.res = False self.exception = exception else: if not self.res: self.error = True if self.callback and CONFIG['thread']: # Post to GTK queue to be run by the main thread GLib.idle_add(self.process) return True def run(self, process_exception_p=True, callback=None): self.process_exception_p = process_exception_p self.callback = callback if callback and CONFIG['thread']: # Parent is only useful if it is asynchronous # otherwise the cursor is not updated. self.parent = get_toplevel_window() self._cursor_timeout = GLib.timeout_add(3000, self._set_cursor) Thread(target=self.start).start() return else: self.start() return self.process() def _set_cursor(self): self._cursor_timeout = None if self.parent: window = self.parent.get_window() if window: display = window.get_display() watch = Gdk.Cursor.new_for_display( display, Gdk.CursorType.WATCH) window.set_cursor(watch) def process(self): if self._cursor_timeout: GLib.source_remove(self._cursor_timeout) self._cursor_timeout = None if self.parent and self.parent.get_window(): self.parent.get_window().set_cursor(None) if self.exception and self.process_exception_p: def rpc_execute(*args): return RPCProgress('execute', args).run( self.process_exception_p, self.callback) try: return process_exception( self.exception, *self.args, rpc_execute=rpc_execute) except RPCException as exception: self.exception = exception def return_(): if self.exception: if not isinstance(self.exception, RPCException): raise RPCException(self.exception) raise self.exception else: return self.res if self.callback: self.callback(return_) else: return return_() def RPCExecute(*args, **kwargs): rpc_context = rpc.CONTEXT.copy() if kwargs.get('context'): rpc_context.update(kwargs['context']) args = args + (rpc_context,) process_exception = kwargs.get('process_exception', True) callback = kwargs.get('callback') return RPCProgress('execute', args).run(process_exception, callback) def RPCContextReload(callback=None): def update(context): rpc.context_reset() try: rpc.CONTEXT.update(context()) except RPCException: pass if callback: callback() context = RPCExecute( 'model', 'res.user', 'get_preferences', True, callback=update if callback else None) if not callback: rpc.context_reset() rpc.CONTEXT.update(context) class Tooltips(object): _tooltips = None def set_tip(self, widget, tip_text): if hasattr(widget, 'set_tooltip_text'): return widget.set_tooltip_text(tip_text) if not self._tooltips: self._tooltips = Gtk.Tooltips() return self._tooltips.set_tip(widget, tip_text) def enable(self): if self._tooltips: self._tooltips.enable() def disable(self): if self._tooltips: self._tooltips.disable() COLOR_SCHEMES = { 'red': '#cf1d1d', 'green': '#3fb41b', 'blue': '#224565', 'grey': '#444444', 'black': '#000000', 'darkcyan': '#305755', } def filter_domain(domain): ''' Return the biggest subset of domain with only AND operator ''' res = [] for arg in domain: if isinstance(arg, str): if arg == 'OR': res = [] break continue if isinstance(arg, tuple): res.append(arg) elif isinstance(arg, list): res.extend(filter_domain(arg)) return res def timezoned_date(date, reverse=False): try: from dateutil.tz.win import tzwinlocal as tzlocal except ImportError: from dateutil.tz import tzlocal from dateutil.tz import tzutc lzone = tzlocal() szone = tzutc() if reverse: lzone, szone = szone, lzone return date.replace(tzinfo=szone).astimezone(lzone).replace(tzinfo=None) def untimezoned_date(date): return timezoned_date(date, reverse=True) def humanize(size, suffix=''): if 0 < abs(size) < 1: for u in ['', 'm', 'µ', 'n', 'p', 'f', 'a', 'z', 'y', 'r', 'q']: if abs(size) >= 0.01: break size *= 1000.0 else: size /= 1000.0 else: for u in ['', 'k', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y', 'R', 'Q']: if abs(size) <= 1000: break size /= 1000.0 else: size *= 1000.0 if isinstance(size, int) or size.is_integer(): size = locale.localize(str(int(size))) elif abs(size) < 0.01: size = locale.localize( '{0:f}'.format(size).rstrip('0').rstrip('.')) else: size = locale.localize( '{0:.{1}f}'.format(size, 2).rstrip('0').rstrip('.')) return ''.join([size, u, suffix]) def get_hostname(netloc): if '[' in netloc and ']' in netloc: hostname = netloc.split(']')[0][1:] elif ':' in netloc: hostname = netloc.split(':')[0] else: hostname = netloc return hostname.strip() def get_port(netloc): netloc = netloc.split(']')[-1] if ':' in netloc: try: return int(netloc.split(':')[1]) except ValueError: pass return 8000 def resize_pixbuf(pixbuf, width, height): img_height = pixbuf.get_height() height = min(img_height, height) if height != -1 else img_height img_width = pixbuf.get_width() width = min(img_width, width) if width != -1 else img_width if img_width / width < img_height / height: width = float(img_width) / float(img_height) * float(height) else: height = float(img_height) / float(img_width) * float(width) return pixbuf.scale_simple( int(width), int(height), GdkPixbuf.InterpType.BILINEAR) def _data2pixbuf(data, width=None, height=None): loader = GdkPixbuf.PixbufLoader() if width and height: loader.set_size(width, height) loader.write(data) loader.close() return loader.get_pixbuf() def data2pixbuf(data, width=None, height=None): if data: try: return _data2pixbuf(data, width, height) except GLib.GError: pass def apply_label_attributes(label, readonly, required): if not readonly: widget_class(label, 'editable', True) widget_class(label, 'required', required) else: widget_class(label, 'editable', False) widget_class(label, 'required', False) def ellipsize(string, length): if len(string) <= length: return string ellipsis = _('...') return string[:length - len(ellipsis)] + ellipsis def get_align(float_, expand=True): "Convert float align into Gtk.Align" value = float(float_) if expand: return Gtk.Align.FILL if value < 0.5: return Gtk.Align.START elif value == 0.5: return Gtk.Align.CENTER else: return Gtk.Align.END def date_format(format_=None): return format_ or rpc.CONTEXT.get('locale', {}).get('date', '%x') def idle_add(func): @wraps(func) def wrapper(*args, **kwargs): GLib.idle_add(func, *args, **kwargs) return wrapper def setup_window(window): if sys.platform == 'darwin': window.set_mnemonic_modifier(Gdk.ModifierType.CONTROL_MASK) def play_sound(self, sound='success'): try: from playsound import playsound playsound(os.path.join(SOUNDS_DIR, f'{sound}.wav')) except ImportError: pass def get_gdk_backend(): if sys.platform == 'darwin': return 'macos' elif sys.platform == 'win32': return 'win32' else: dm = Gdk.DisplayManager.get() default = dm.props.default_display dm_class_name = default.__class__.__name__.lower() if 'wayland' in dm_class_name: return 'wayland' return 'x11' ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685599.0 tryton-7.0.24/tryton/common/completion.py0000644000175000017500000000571214517761237016647 0ustar00cedced# This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. import gettext import logging from gi.repository import GLib, Gtk from tryton.common import RPCExecute from tryton.config import CONFIG from tryton.exceptions import TrytonError, TrytonServerError _ = gettext.gettext logger = logging.getLogger(__name__) def get_completion(search=True, create=True): "Return a EntryCompletion" completion = Gtk.EntryCompletion() completion.set_match_func(lambda *a: True) completion.set_model(Gtk.ListStore(str, object, object)) completion.set_text_column(0) completion.props.popup_set_width = False if search: completion.insert_action_markup(0, _('Search...')) if create: completion.insert_action_markup(1, _('Create...')) completion._allow_create = create return completion def update_completion(entry, record, field, model, domain=None): "Update entry completion" def update(search_text, domain): if not entry.props.window: return False if search_text != entry.get_text(): return False completion = entry.get_completion() completion_model = completion.get_model() if not search_text or not model: completion_model.clear() completion_model.search_text = search_text return False if getattr(completion_model, 'search_text', None) == search_text: return False if domain is None: domain = field.domain_get(record) context = field.get_search_context(record) order = field.get_search_order(record) def callback(results): try: results = results() except (TrytonError, TrytonServerError): results = [] if search_text != entry.get_text(): return False completion_model.clear() for result in results: if result['id'] is not None or completion._allow_create: if result['id'] is None: name = _('Create "%s"...') % result['name'] else: name = result['name'] completion_model.append(( name, result['id'], result['defaults'])) completion_model.search_text = search_text # Force display of popup entry.emit('changed') try: RPCExecute( 'model', model, 'autocomplete', search_text, domain, CONFIG['client.limit'], order, context=context, process_exception=False, callback=callback) except Exception: logger.warning( "Unable to search for completion of %s", model, exc_info=True) return False search_text = entry.get_text() GLib.timeout_add(300, update, search_text, domain) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1725720314.0 tryton-7.0.24/tryton/common/datetime_.py0000644000175000017500000004765214667063372016443 0ustar00cedced# This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. import datetime import gettext from dateutil.parser import parse from dateutil.relativedelta import relativedelta from gi.repository import Gdk, GObject, Gtk from .common import IconFactory __all__ = [ 'Date', 'CellRendererDate', 'Time', 'CellRendererTime', 'DateTime', 'date_parse'] _ = gettext.gettext def _fix_format(format_): if '%Y' in format_: if (datetime.date.min.strftime('%Y') != '0001' and datetime.date.min.strftime('%4Y') == '0001'): format_ = format_.replace('%Y', '%4Y') return format_ def date_parse(text, format_='%x'): try: return datetime.datetime.strptime(text, format_) except ValueError: pass formatted_date = datetime.date(1988, 7, 16).strftime(format_) try: dayfirst = formatted_date.index('16') == 0 except ValueError: dayfirst = False try: monthfirst = formatted_date.index('7') <= 1 except ValueError: monthfirst = False yearfirst = not dayfirst and not monthfirst return parse(text, dayfirst=dayfirst, yearfirst=yearfirst, ignoretz=True) class Date(Gtk.Entry): __gtype_name__ = 'Date' __gproperties__ = { 'value': (GObject.TYPE_PYOBJECT, _('Value'), _('Displayed value'), GObject.ParamFlags.READABLE | GObject.ParamFlags.WRITABLE), 'format': (GObject.TYPE_STRING, '%x', _('Format'), _('Display format'), GObject.ParamFlags.READABLE | GObject.ParamFlags.WRITABLE), } __gsignals__ = { 'date-changed': ( GObject.SignalFlags.RUN_LAST | GObject.SignalFlags.ACTION, GObject.TYPE_NONE, ()), } def __init__(self): self.__date = None self.__format = '%x' Gtk.Entry.__init__(self) self.set_width_chars(20) self.connect('focus-out-event', self.focus_out) self.connect('activate', self.activate) # Calendar Popup self.set_icon_from_pixbuf( Gtk.EntryIconPosition.PRIMARY, IconFactory.get_pixbuf('tryton-date', Gtk.IconSize.MENU)) self.set_icon_tooltip_text( Gtk.EntryIconPosition.PRIMARY, _('Open the calendar')) self.connect('icon-press', self.icon_press) self.__cal_popup = Gtk.Window(type=Gtk.WindowType.POPUP) self.__cal_popup.set_events( self.__cal_popup.get_events() | Gdk.EventMask.KEY_PRESS_MASK) self.__cal_popup.set_resizable(False) self.__cal_popup.connect('delete-event', self.cal_popup_closed) self.__cal_popup.connect('key-press-event', self.cal_popup_key_pressed) self.__cal_popup.connect('button-press-event', self.cal_popup_button_pressed) self.__calendar = Gtk.Calendar() cal_options = ( Gtk.CalendarDisplayOptions.SHOW_DAY_NAMES | Gtk.CalendarDisplayOptions.SHOW_HEADING | Gtk.CalendarDisplayOptions.SHOW_WEEK_NUMBERS) self.__calendar.set_display_options(cal_options) self.__cal_popup.add(self.__calendar) self.__calendar.connect('day-selected', self.cal_popup_changed) self.__calendar.connect('day-selected-double-click', self.cal_popup_double_click) self.__calendar.show() def parse(self): text = self.get_text() date = None if text: try: date = date_parse(text, self.__format).date() except (ValueError, OverflowError): pass self.__date = date def update_label(self): if not self.__date: self.set_text('') return self.set_text(self.__date.strftime(self.__format)) def icon_press(self, entry, icon_pos, event): self.grab_focus() if icon_pos == Gtk.EntryIconPosition.PRIMARY: self.cal_popup_open() def cal_popup_open(self): self.parse() if self.__date: self.__calendar.select_month( self.__date.month - 1, self.__date.year) self.__calendar.select_day(self.__date.day) self.__cal_popup.set_transient_for(self.get_toplevel()) # Show popup before because position needs the popup allocation popup_show(self.__cal_popup) popup_position(self, self.__cal_popup) def cal_popup_changed(self, calendar): year, month, day = self.__calendar.get_date() self.__date = datetime.date(year, month + 1, day) self.update_label() self.emit('date-changed') def cal_popup_double_click(self, calendar): self.cal_popup_hide() def cal_popup_key_pressed(self, calendar, event): if event.keyval != Gdk.KEY_Escape: return False self.stop_emission_by_name('key-press-event') self.cal_popup_hide() return True def cal_popup_button_pressed(self, calendar, event): child = event.window window = calendar.get_window() if child != window: while child: if child == window: return False child = child.get_parent() self.cal_popup_hide() return True def cal_popup_closed(self, popup): self.cal_popup_hide() return True def cal_popup_hide(self): popup_hide(self.__cal_popup) self.grab_focus() self.emit('date-changed') def cal_popup_is_visible(self): return self.__cal_popup.is_visible() def focus_out(self, entry, event): previous_date = self.__date self.parse() self.update_label() if self.__date != previous_date: self.emit('date-changed') return False def activate(self, entry=None): self.parse() self.update_label() self.emit('date-changed') return False def do_set_property(self, prop, value): if prop.name == 'value': if isinstance(value, str): self.set_text(value) self.parse() value = self.__date if value: if isinstance(value, datetime.datetime): value = value.date() assert isinstance(value, datetime.date), value self.__date = value self.update_label() elif prop.name == 'format': self.__format = _fix_format(value) self.update_label() def do_get_property(self, prop): if prop.name == 'value': return self.__date elif prop.name == 'format': return self.__format GObject.type_register(Date) class CellRendererDate(Gtk.CellRendererText): __gproperties__ = { 'format': (GObject.TYPE_STRING, _('Format'), _('Display format'), '%x', GObject.ParamFlags.READABLE | GObject.ParamFlags.WRITABLE), } def __init__(self): self.__format = '%x' self.__entry = None self.__focus_out_id = 0 Gtk.CellRendererText.__init__(self) def do_set_property(self, prop, value): if prop.name == 'format': self.__format = _fix_format(value) return Gtk.CellRendererText.set_property(self, prop, value) def do_get_property(self, prop): if prop.name == 'format': return self.__format return Gtk.CellRendererText.get_property(self, prop) def do_start_editing( self, event, widget, path, background_area, cell_area, flags): if not self.props.editable: return self.__entry = add_operators(Date()) # TODO add_operators has option self.__entry.props.format = self.props.format self.__entry.props.value = self.props.text self.__entry.set_has_frame(False) self.__entry.connect('editing-done', self.__editing_done) self.__focus_out_id = self.__entry.connect( 'focus-out-event', self.__focus_out_event) # XXX focus-out-event self.__entry.show() return self.__entry def __editing_done(self, entry): self.__entry = None if self.__focus_out_id: entry.disconnect(self.__focus_out_id) canceled = entry.props.editing_canceled self.stop_editing(canceled) if canceled: return # TODO emit edited def __focus_out_event(self, entry, event): if entry.cal_popup_is_visible(): return True entry.props.editing_canceled = True entry.editing_done() entry.remove_widget() return False GObject.type_register(CellRendererDate) class Time(Gtk.ComboBox): __gtype_name__ = 'Time' __gproperties__ = { 'value': (GObject.TYPE_PYOBJECT, _('Value'), _('Displayed value'), GObject.ParamFlags.READABLE | GObject.ParamFlags.WRITABLE), 'format': (GObject.TYPE_STRING, _('Format'), _('Display format'), '%X', GObject.ParamFlags.READABLE | GObject.ParamFlags.WRITABLE), } __gsignals__ = { 'time-changed': ( GObject.SignalFlags.RUN_LAST | GObject.SignalFlags.ACTION, GObject.TYPE_NONE, ()), } def __init__(self): self.__time = None self.__format = '%X' Gtk.ComboBox.__init__(self, has_entry=True) self.__entry = self.get_child() self.__entry.set_width_chars(10) self.__entry.connect('focus-out-event', self.focus_out) self.__entry.connect('activate', self.activate) self.connect('changed', self.changed) self.__model = Gtk.ListStore( GObject.TYPE_STRING, GObject.TYPE_PYOBJECT) self.update_model() self.set_model(self.__model) self.set_entry_text_column(0) def parse(self): text = self.__entry.get_text() time = None if text: try: time = date_parse(text).time() except (ValueError, OverflowError): pass self.__time = time def update_label(self): if self.__time is None: self.__entry.set_text('') return self.__entry.set_text(self.__time.strftime(self.__format)) def update_model(self): self.__model.clear() timelist_set_list( self.__model, datetime.time(0, 0), datetime.time(23, 59), self.__format) def focus_out(self, entry, event): self.parse() self.update_label() self.emit('time-changed') return False def activate(self, entry=None): self.parse() self.update_label() self.emit('time-changed') return False def changed(self, combobox): # "changed" signal is also triggered by text editing # so only parse when a row is active if combobox.get_active_iter(): self.parse() self.update_label() self.emit('time-changed') return False def do_set_property(self, prop, value): if prop.name == 'value': if isinstance(value, str): self.__entry.set_text(value) self.parse() value = self.__time if value: if isinstance(value, datetime.datetime): value = value.time() self.__time = value self.update_label() elif prop.name == 'format': self.__format = _fix_format(value) self.update_label() self.update_model() def do_get_property(self, prop): if prop.name == 'value': return self.__time elif prop.name == 'format': return self.__format GObject.type_register(Time) class CellRendererTime(Gtk.CellRendererCombo): __gproperties__ = { 'format': (GObject.TYPE_STRING, '%X', _('Format'), _('Display format'), GObject.ParamFlags.READABLE | GObject.ParamFlags.WRITABLE), } def __init__(self): self.__format = '%X' self.__combo = None self.__focus_out_id = 0 Gtk.CellRendererText.__init__(self) def do_set_property(self, prop, value): if prop.name == 'format': self.__format = _fix_format(value) return Gtk.CellRendererText.set_property(self, prop, value) def do_get_property(self, prop): if prop.name == 'format': return self.__format return Gtk.CellRendererText.get_property(self, prop) def do_start_editing( self, event, widget, path, background_area, cell_area, flags): if not self.props.editable: return self.__combo = add_operators(Time()) # TODO add_operators has option self.__combo.props.format = self.props.format self.__combo.props.value = self.props.text self.__combo.props.has_frame = False self.__combo.connect('editing-done', self.__editing_done) # TODO: connect to changed self.__focus_out_id = self.__combo.connect( 'focus-out-event', self.__focus_out_event) self.__combo.show() return self.__combo def __editing_done(self, combo): self.__combo = None canceled = combo.props.editing_canceled self.stop_editing(canceled) if canceled: return # TODO emit edited def __focus_out_event(self, combo, event): self.__editing_done(combo) return False GObject.type_register(CellRendererTime) class DateTime(Gtk.HBox): __gtype_name__ = 'DateTime' __gproperties__ = { 'value': (GObject.TYPE_PYOBJECT, _('Value'), _('Displayed value'), GObject.ParamFlags.READABLE | GObject.ParamFlags.WRITABLE), 'date-format': (GObject.TYPE_STRING, '%x', _('Date Format'), _('Displayed date format'), GObject.ParamFlags.READABLE | GObject.ParamFlags.WRITABLE), 'time-format': (GObject.TYPE_STRING, '%X', _('Date Format'), _('Displayed date format'), GObject.ParamFlags.READABLE | GObject.ParamFlags.WRITABLE), } __gsignals__ = { 'datetime-changed': ( GObject.SignalFlags.RUN_LAST | GObject.SignalFlags.ACTION, GObject.TYPE_NONE, ()), } def __init__(self): Gtk.HBox.__init__(self, spacing=4) self.__date = Date() self.pack_start(self.__date, expand=True, fill=True, padding=0) self.__date.show() self.__date.connect('date-changed', lambda e: self.emit('datetime-changed')) self.__time = Time() self.pack_start(self.__time, expand=True, fill=True, padding=0) self.__time.show() self.__time.connect('time-changed', lambda e: self.emit('datetime-changed')) def parse(self): self.__date.parse() self.__time.parse() def do_set_property(self, prop, value): if prop.name == 'value': self.__date.props.value = value self.__time.props.value = value elif prop.name == 'date-format': self.__date.props.format = value elif prop.name == 'time-format': self.__time.props.format = value def do_get_property(self, prop): if prop.name == 'value': date = self.__date.props.value time = self.__time.props.value or datetime.time() if date: return datetime.datetime.combine(date, time) else: return elif prop.name == 'date-format': return self.__date.props.format elif prop.name == 'time-format': return self.__time.props.format def modify_bg(self, state, color): self.__date.modify_bg(state, color) self.__time.child.modify_bg(state, color) def modify_base(self, state, color): self.__date.modify_base(state, color) self.__time.child.modify_base(state, color) def modify_fg(self, state, color): self.__date.modify_fg(state, color) self.__time.child.modify_fg(state, color) def modify_text(self, state, color): self.__date.modify_text(state, color) self.__time.child.modify_text(state, color) GObject.type_register(DateTime) def popup_position(widget, popup): allocation = widget.get_allocation() popup_allocation = popup.get_allocation() x, y = widget.get_window().get_root_coords(allocation.x, allocation.y) display = widget.get_display() monitor = display.get_monitor_at_window(widget.get_window()) monitor_geometry = monitor.get_geometry() if (monitor_geometry.height < y + allocation.height + popup_allocation.height): y -= popup_allocation.height else: y += allocation.height if monitor_geometry.width < x + popup_allocation.width: x -= popup_allocation.width popup.move(x, y) def popup_show(popup): popup.show() popup.grab_focus() popup.grab_add() window = popup.get_window() display = window.get_display() seat = display.get_default_seat() seat.grab(window, Gdk.SeatCapabilities.ALL, True, None, None, None, None) def popup_hide(popup): popup.hide() popup.grab_remove() window = popup.get_window() display = window.get_display() seat = display.get_default_seat() seat.ungrab() def timelist_set_list(model, min_, max_, format_): time = min_ delta = 30 while time < max_: model.append((time.strftime(format_), time)) hour = time.hour minute = time.minute + delta hour, minute = divmod(minute, 60) hour += time.hour if hour >= 24: break time = datetime.time(hour, minute) def add_operators(widget): def key_press(editable, event): if not editable.get_editable(): return False if event.keyval in OPERATORS: value = widget.props.value if value: if isinstance(value, datetime.time): value = datetime.datetime.combine( datetime.date.today(), value) try: widget.props.value = value + OPERATORS[event.keyval] except TypeError: return False return True elif event.keyval in (Gdk.KEY_KP_Equal, Gdk.KEY_equal): widget.props.value = datetime.datetime.now() return True return False if isinstance(widget, DateTime): for child in widget.get_children(): add_operators(child) return widget if isinstance(widget, Gtk.ComboBox): editable = widget.get_child() else: editable = widget editable.connect('key-press-event', key_press) return widget OPERATORS = { Gdk.KEY_S: relativedelta(seconds=-1), Gdk.KEY_s: relativedelta(seconds=1), Gdk.KEY_I: relativedelta(minutes=-1), Gdk.KEY_i: relativedelta(minutes=1), Gdk.KEY_H: relativedelta(hours=-1), Gdk.KEY_h: relativedelta(hours=1), Gdk.KEY_D: relativedelta(days=-1), Gdk.KEY_d: relativedelta(days=1), Gdk.KEY_W: relativedelta(weeks=-1), Gdk.KEY_w: relativedelta(weeks=1), Gdk.KEY_M: relativedelta(months=-1), Gdk.KEY_m: relativedelta(months=1), Gdk.KEY_Y: relativedelta(years=-1), Gdk.KEY_y: relativedelta(years=1), } if __name__ == '__main__': win = Gtk.Window() win.connect('delete-event', Gtk.main_quit) v = Gtk.VBox() v.show() d = add_operators(Date()) d.show() v.pack_start(d, expand=False, fill=False, padding=0) t = add_operators(Time()) t.show() v.pack_start(t, expand=False, fill=False, padding=0) t = add_operators(Time()) t.props.format = '%H:%M' t.show() v.pack_start(t, expand=False, fill=False, padding=0) dt = add_operators(DateTime()) dt.show() v.pack_start(dt, expand=False, fill=False, padding=0) win.add(v) win.show() Gtk.main() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1732474477.0 tryton-7.0.24/tryton/common/domain_inversion.py0000644000175000017500000004404014720673155020033 0ustar00cedced# This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. import datetime import operator import re from collections import defaultdict from functools import partial, reduce def sql_like(value, pattern, ignore_case=True): flag = re.IGNORECASE if ignore_case else 0 escape = False chars = [] for char in re.split(r'(\\|.)', pattern)[1::2]: if escape: if char in ('%', '_'): chars.append(char) else: chars.extend(['\\', char]) escape = False elif char == '\\': escape = True elif char == '_': chars.append('.') elif char == '%': chars.append('.*') else: chars.append(re.escape(char)) regexp = re.compile(''.join(chars), flag) return bool(regexp.fullmatch(value)) like = partial(sql_like, ignore_case=False) ilike = partial(sql_like, ignore_case=True) def in_(a, b): if isinstance(a, (list, tuple)): if isinstance(b, (list, tuple)): return any(operator.contains(b, x) for x in a) else: return operator.contains(a, b) else: return operator.contains(b, a) OPERATORS = defaultdict(lambda: lambda a, b: True) OPERATORS.update({ '=': operator.eq, '>': operator.gt, '<': operator.lt, '<=': operator.le, '>=': operator.ge, '!=': operator.ne, 'in': in_, 'not in': lambda a, b: not in_(a, b), 'ilike': ilike, 'not ilike': lambda a, b: not ilike(a, b), 'like': like, 'not like': lambda a, b: not like(a, b), }) def locale_part(expression, field_name, locale_name='id'): if expression == field_name: return locale_name if '.' in expression: fieldname, local = expression.split('.', 1) return local return expression def is_leaf(expression): return (isinstance(expression, (list, tuple)) and len(expression) > 2 and isinstance(expression[1], str)) def constrained_leaf(part, boolop=operator.and_): field, operand, value = part[:3] if operand == '=' and boolop == operator.and_: # We should consider that other domain inversion will set a correct # value to this field return True return False def eval_leaf(part, context, boolop=operator.and_): field, operand, value = part[:3] if '.' in field: # In the case where the leaf concerns a m2o then having a value in the # evaluation context is deemed suffisant return bool(context.get(field.split('.')[0])) context_field = context.get(field) if (operand not in {'=', '!='} and (context_field is None or value is None) and not (operand in {'in', 'not in'} and context_field is None and (isinstance(value, (list, tuple)) and None in value))): return if isinstance(context_field, datetime.date) and not value: if isinstance(context_field, datetime.datetime): value = datetime.datetime.min else: value = datetime.date.min if isinstance(value, datetime.date) and not context_field: if isinstance(value, datetime.datetime): context_field = datetime.datetime.min else: context_field = datetime.date.min if isinstance(context_field, (list, tuple)) and value is None: value = type(context_field)() if (isinstance(context_field, str) and isinstance(value, (list, tuple))): try: value = '%s,%s' % tuple(value) except TypeError: pass elif (isinstance(context_field, (list, tuple)) and isinstance(value, str)): try: context_field = '%s,%s' % tuple(context_field) except TypeError: pass elif ((isinstance(context_field, list) and isinstance(value, tuple)) or (isinstance(context_field, tuple) and isinstance(value, list))): context_field = list(context_field) value = list(value) if (operand in ('=', '!=') and isinstance(context_field, (list, tuple)) and isinstance(value, int)): operand = { '=': 'in', '!=': 'not in', }[operand] try: return OPERATORS[operand](context_field, value) except TypeError: return False def inverse_leaf(domain): if domain in ('AND', 'OR'): return domain elif is_leaf(domain): if 'child_of' in domain[1] and '.' not in domain[0]: if len(domain) == 3: return domain else: return [domain[3]] + list(domain[1:]) return domain else: return list(map(inverse_leaf, domain)) def filter_leaf(domain, field, model): if domain in ('AND', 'OR'): return domain elif is_leaf(domain): if domain[0].startswith(field) and len(domain) > 3: if domain[3] != model: return ('id', '=', None) return domain else: return [filter_leaf(d, field, model) for d in domain] def prepare_reference_domain(domain, reference): "convert domain to replace reference fields by their local part" def value2reference(value): model, ref_id = None, None if isinstance(value, str) and ',' in value: model, ref_id = value.split(',', 1) if ref_id != '%': try: ref_id = int(ref_id) except ValueError: model, ref_id = None, value elif (isinstance(value, (list, tuple)) and len(value) == 2 and isinstance(value[0], str) and (isinstance(value[1], int) or value[1] == '%')): model, ref_id = value else: ref_id = value return model, ref_id if domain in ('AND', 'OR'): return domain elif is_leaf(domain): if domain[0] == reference: if domain[1] in {'=', '!='}: model, ref_id = value2reference(domain[2]) if model is not None: if ref_id == '%': if domain[1] == '=': return [reference + '.id', '!=', None, model] else: return [reference, 'not like', domain[2]] return [reference + '.id', domain[1], ref_id, model] elif domain[1] in {'in', 'not in'}: model_values = {} for value in domain[2]: model, ref_id = value2reference(value) if model is None: break model_values.setdefault(model, []).append(ref_id) else: new_domain = ['OR'] if domain[1] == 'in' else ['AND'] for model, ref_ids in model_values.items(): if '%' in ref_ids: if domain[1] == 'in': new_domain.append( [reference + '.id', '!=', None, model]) else: new_domain.append( [reference, 'not like', model + ',%']) else: new_domain.append( [reference + '.id', domain[1], ref_ids, model]) return new_domain return [] return domain else: return [prepare_reference_domain(d, reference) for d in domain] def extract_reference_models(domain, field_name): "returns the set of the models available for field_name" if domain in ('AND', 'OR'): return set() elif is_leaf(domain): local_part = domain[0].split('.', 1)[0] if local_part == field_name and len(domain) > 3: return {domain[3]} return set() else: return reduce(operator.or_, (extract_reference_models(d, field_name) for d in domain)) def eval_domain(domain, context, boolop=operator.and_): "compute domain boolean value according to the context" if is_leaf(domain): return eval_leaf(domain, context, boolop=boolop) elif not domain and boolop is operator.and_: return True elif not domain and boolop is operator.or_: return False elif domain[0] == 'AND': return eval_domain(domain[1:], context) elif domain[0] == 'OR': return eval_domain(domain[1:], context, operator.or_) else: return boolop(bool(eval_domain(domain[0], context)), bool(eval_domain(domain[1:], context, boolop))) def localize_domain(domain, field_name=None, strip_target=False): "returns only locale part of domain. eg: langage.code -> code" if domain in ('AND', 'OR', True, False): return domain elif is_leaf(domain): if 'child_of' in domain[1]: if domain[0].count('.'): _, target_part = domain[0].split('.', 1) return [target_part] + list(domain[1:]) if len(domain) == 3: return domain else: return [domain[3]] + list(domain[1:-1]) locale_name = 'id' if isinstance(domain[2], str): locale_name = 'rec_name' n = 3 if strip_target else 4 return [locale_part(domain[0], field_name, locale_name)] \ + list(domain[1:n]) + list(domain[4:]) else: return [localize_domain(part, field_name, strip_target) for part in domain] def _sort_key(domain): if not domain: return (0, tuple()) elif is_leaf(domain): return (1, tuple((type(x).__name__, x) for x in domain)) elif domain in ['AND', 'OR']: return (0, domain) else: content = tuple(_sort_key(e) for e in domain) nestedness = max(k[0] for k in content) return (nestedness + 1, content) def sort(domain): "Sort a domain" if not domain: return domain elif is_leaf(domain): return domain elif domain in ['AND', 'OR']: return domain else: return sorted((sort(e) for e in domain), key=_sort_key) def bool_operator(domain): "Returns the boolean operator used by a domain" bool_op = 'AND' if domain and domain[0] in ['AND', 'OR']: bool_op = domain[0] return bool_op def simplify_nested(domain): """Simplify extra domain markers""" if not domain: return [] elif is_leaf(domain): return [domain] elif domain in ['OR', 'AND']: return [domain] elif isinstance(domain, list) and len(domain) == 1: return simplify_nested(domain[0]) else: simplified = [] domain_op = bool_operator(domain) for branch in domain: simplified_branch = simplify_nested(branch) if (bool_operator(simplified_branch) == domain_op or len(simplified_branch) == 1): if (simplified and simplified_branch and simplified_branch[0] in ['AND', 'OR']): simplified.extend(simplified_branch[1:]) else: simplified.extend(simplified_branch) else: simplified.append(simplified_branch) return simplified def simplify_duplicate(domain): """Remove duplicates subdomain from domain""" dedup_branches = [] bool_op = None if domain[0] in ['AND', 'OR']: bool_op, *domain = domain for branch in domain: simplified_branch = simplify(branch) if not simplified_branch: if bool_op == 'OR': return [] else: continue elif simplified_branch not in dedup_branches: dedup_branches.append(simplified_branch) if bool_op and len(dedup_branches) > 1: dedup_branches.insert(0, bool_op) return dedup_branches def simplify_AND(domain): """Remove useless ANDs""" if is_leaf(domain): return domain elif domain == 'OR': return domain else: return [simplify_AND(e) for e in domain if e != 'AND'] def simplify(domain): """Remove duplicate expressions and useless OR/AND""" if is_leaf(domain): return [domain] elif not domain: return domain else: return simplify_nested(simplify_duplicate(domain)) def canonicalize(domain): """Returns the canonical version of a domain. The canonical version of a domain is one where the domain is both simplified and sorted """ return simplify_AND(sort(simplify(domain))) def merge(domain, domoperator=None): if not domain or domain in ('AND', 'OR'): return [] domain_type = 'OR' if domain[0] == 'OR' else 'AND' if is_leaf(domain): return [domain] elif domoperator is None: return [domain_type] + reduce(operator.add, [merge(e, domain_type) for e in domain]) elif domain_type == domoperator: return reduce(operator.add, [merge(e, domain_type) for e in domain]) else: # without setting the domoperator return [merge(domain)] def concat(*domains, **kwargs): domoperator = kwargs.get('domoperator') result = [] if domoperator: result.append(domoperator) for domain in domains: if domain: result.append(domain) return simplify(merge(result)) def unique_value(domain, single_value=True): "Return if unique, the field and the value" if (isinstance(domain, list) and len(domain) == 1): name, operator, value, *model = domain[0] count = name.count('.') if ( (operator == '=' or (single_value and operator == 'in' and len(value) == 1)) and (not count or (count == 1 and model and name.endswith('.id')))): if operator == 'in' and single_value: value = value[0] if model and name.endswith('.id'): model = model[0] value = [model, value] return True, name, value return False, None, None def parse(domain): if is_leaf(domain): return domain elif not domain: return And([]) elif domain[0] == 'OR': return Or(domain[1:]) else: return And(domain[1:] if domain[0] == 'AND' else domain) def domain_inversion(domain, symbol, context=None): """compute an inversion of the domain eventually the context is used to simplify the expression""" if context is None: context = {} expression = parse(domain) if symbol not in expression.variables: return True return expression.inverse(symbol, context) class And(object): def __init__(self, expressions): self.branches = list(map(parse, expressions)) self.variables = set() for expression in self.branches: if is_leaf(expression): self.variables.add(self.base(expression[0])) elif isinstance(expression, And): self.variables |= expression.variables def base(self, expression): if '.' not in expression: return expression else: return expression.split('.')[0] def inverse(self, symbol, context): result = [] for part in self.branches: if isinstance(part, And): part_inversion = part.inverse(symbol, context) evaluated = isinstance(part_inversion, bool) if symbol not in part.variables: continue if not evaluated: result.append(part_inversion) elif part_inversion: continue else: return False elif is_leaf(part) and self.base(part[0]) == symbol: result.append(part) else: field = part[0] if (field not in context or field in context and (eval_leaf(part, context, operator.and_) or constrained_leaf(part, operator.and_))): result.append(True) else: return False result = [e for e in result if e is not True] if result == []: return True else: return simplify(result) class Or(And): def inverse(self, symbol, context): result = [] known_variables = set(context.keys()) if not known_variables >= (self.variables - {symbol}): # In this case we don't know enough about this OR part, we # consider it to be True (because people will have the constraint # on this part later). return True for part in self.branches: if isinstance(part, And): part_inversion = part.inverse(symbol, context) evaluated = isinstance(part_inversion, bool) if symbol not in part.variables: if evaluated and part_inversion: return True continue if not evaluated: result.append(part_inversion) elif part_inversion: return True else: continue elif is_leaf(part) and self.base(part[0]) == symbol: result.append(part) else: field = part[0] field = self.base(field) if (field in context and (eval_leaf(part, context, operator.or_) or constrained_leaf(part, operator.or_))): return True elif (field in context and not eval_leaf(part, context, operator.or_)): result.append(False) result = [e for e in result if e is not False] if result == []: return False else: return simplify(['OR'] + result) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1743785596.0 tryton-7.0.24/tryton/common/domain_parser.py0000644000175000017500000007157514774007174017331 0ustar00cedced# This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. import datetime import decimal import gettext import io import locale from collections import OrderedDict from decimal import Decimal from shlex import shlex from types import GeneratorType from tryton.common import date_format, timezoned_date, untimezoned_date from tryton.common.datetime_ import date_parse from tryton.common.timedelta import format as timedelta_format from tryton.common.timedelta import parse as timedelta_parse from tryton.pyson import PYSONDecoder __all__ = ['DomainParser'] _ = gettext.gettext ListGeneratorType = type(iter([])) TupleGeneratorType = type(iter(())) OPERATORS = ( '!=', '<=', '>=', '=', '!', '<', '>', ) class udlex(shlex): "A lexical analyzer class for human domain syntaxes." def __init__(self, instream=None): shlex.__init__(self, io.StringIO(instream), posix=True) self.commenters = '' self.quotes = '"' class DummyWordchars(object): "Simulate str that contains all chars except somes" def __contains__(self, item): return item not in (':', '>', '<', '=', '!', '"', ';', '(', ')') self.wordchars = DummyWordchars() def isgenerator(value): "Test if value is a generator type" return isinstance(value, (GeneratorType, ListGeneratorType, TupleGeneratorType)) def rlist(value): "Convert recursivly generator into list" if isgenerator(value) or isinstance(value, list): return [rlist(x) for x in value] return value def simplify(value): "Remove double nested list" if isinstance(value, list): if len(value) == 1 and isinstance(value[0], list): return simplify(value[0]) elif (len(value) == 2 and value[0] in ('AND', 'OR') and isinstance(value[1], list)): return simplify(value[1]) elif (len(value) == 3 and value[0] in ('AND', 'OR') and isinstance(value[1], list) and value[0] == value[1][0]): value = simplify(value[1]) + [value[2]] return [simplify(x) for x in value] return value def group_operator(tokens): "Group token of operators" try: cur = next(tokens) except StopIteration: return nex = None for nex in tokens: if nex == '=' and cur and cur + nex in OPERATORS: yield cur + nex cur = None else: if cur is not None: yield cur cur = nex if cur is not None: yield cur def likify(value, escape='\\'): "Add % if needed" if not value: return '%' escaped = value.replace(escape + '%', '').replace(escape + '_', '') if '%' in escaped or '_' in escaped: return value else: return '%' + value + '%' def is_full_text(value, escape='\\'): escaped = value if escaped.startswith('%') and escaped.endswith('%'): escaped = escaped[1:-1] escaped = escaped.replace(escape + '%', '').replace(escape + '_', '') if '%' in escaped or '_' in escaped: return False return value.startswith('%') and value.endswith('%') def is_like(value, escape='\\'): escaped = value.replace(escape + '%', '').replace(escape + '_', '') return '%' in escaped or '_' in escaped def unescape(value, escape='\\'): return value.replace(escape + '%', '%').replace(escape + '_', '_') def quote(value, empty=False): "Quote string if needed" if not isinstance(value, str): return value if empty and value == '': return '""' if '\\' in value: value = value.replace('\\', '\\\\') if '"' in value: value = value.replace('"', '\\"') for test in (':', ' ', '(', ')') + OPERATORS: if test in value: return '"%s"' % value return value def ending_clause(domain, deep=0): "Return the ending clause" if not domain: return None, deep if isinstance(domain[-1], list): return ending_clause(domain[-1], deep + 1) return domain[-1], deep def replace_ending_clause(domain, clause): "Replace the ending clause" for dom in domain[:-1]: yield dom if isinstance(domain[-1], list): yield replace_ending_clause(domain[-1], clause) else: yield clause def append_ending_clause(domain, clause, deep): "Append clause after the ending clause" if not domain: yield clause return for dom in domain[:-1]: yield dom if isinstance(domain[-1], list): yield append_ending_clause(domain[-1], clause, deep - 1) else: yield domain[-1] if deep == 0: yield clause def default_operator(field): "Return default operator for field" if field['type'] in ('char', 'text', 'many2one', 'many2many', 'one2many', 'reference', 'one2one'): return 'ilike' elif field['type'] == 'multiselection': return 'in' else: return '=' def negate_operator(operator): "Return negate operator" if operator == 'ilike': return 'not ilike' elif operator == '=': return '!=' elif operator == 'in': return 'not in' def time_format(field): return PYSONDecoder({}).decode(field['format']) def split_target_value(field, value): "Split the reference value into target and value" assert field['type'] == 'reference' target = None if isinstance(value, str): for key, text in field['selection']: if value.lower().startswith(text.lower() + ','): target = key value = value[len(text) + 1:] break return target, value def convert_value(field, value, context=None): "Convert value for field" if context is None: context = {} def convert_boolean(): if isinstance(value, str): return any(test.lower().startswith(value.lower()) for test in ( _('y'), _('Yes'), _('True'), _('t'), '1')) def convert_float(): factor = float(field.get('factor', 1)) try: return locale.atof(value) / factor except (ValueError, AttributeError): return def convert_integer(): factor = float(field.get('factor', 1)) try: return int(locale.atof(value) / factor) except (ValueError, AttributeError): return def convert_numeric(): factor = Decimal(field.get('factor', 1)) try: return Decimal(locale.delocalize(value)) / factor except (decimal.InvalidOperation, AttributeError): return def convert_selection(): if isinstance(value, str): for key, text in field['selection']: if value.lower() == text.lower(): return key return value def convert_datetime(): if not value: return format_ = ( date_format(context.get('date_format')) + ' ' + time_format(field)) try: dt = date_parse(value, format_) return untimezoned_date(dt) except ValueError: return def convert_date(): if not value: return format_ = date_format(context.get('date_format')) try: return date_parse(value, format_).date() except (ValueError, TypeError): return def convert_time(): if not value: return try: return date_parse(value).time() except (ValueError, TypeError): return def convert_timedelta(): return timedelta_parse(value, context.get(field.get('converter'))) def convert_many2one(): if value == '': return None return value converts = { 'boolean': convert_boolean, 'float': convert_float, 'integer': convert_integer, 'numeric': convert_numeric, 'selection': convert_selection, 'multiselection': convert_selection, 'reference': convert_selection, 'datetime': convert_datetime, 'timestamp': convert_datetime, 'date': convert_date, 'time': convert_time, 'timedelta': convert_timedelta, 'many2one': convert_many2one, } return converts.get(field['type'], lambda: value)() def format_value(field, value, target=None, context=None, _quote_empty=False): "Format value for field" if context is None: context = {} def format_boolean(): if value is False: return _("False") elif value: return _("True") else: return '' def format_integer(): factor = float(field.get('factor', 1)) if (value or (isinstance(value, (int, float)) and not isinstance(value, bool))): return str(int(value * factor)) return '' def format_float(): if (not value and (not isinstance(value, (int, float, Decimal)) or isinstance(value, bool))): return '' digit = 0 if isinstance(value, Decimal): cast = Decimal else: cast = float factor = cast(field.get('factor', 1)) string_ = str(value * factor) if 'e' in string_: string_, exp = string_.split('e') digit -= int(exp) if '.' in string_: digit += len(string_.rstrip('0').split('.')[1]) return locale.localize( '{0:.{1}f}'.format(value * factor or 0, digit), True) def format_selection(): if isinstance(field['selection'], (tuple, list)): selections = dict(field['selection']) else: selections = {} return selections.get(value, value) or '' def format_reference(): if not target: return format_selection() selections = dict(field['selection']) return '%s,%s' % (selections.get(target, target), value) def format_datetime(): if not value: return '' if not isinstance(value, datetime.datetime): time = datetime.datetime.combine(value, datetime.time.min) else: time = timezoned_date(value) format_ = date_format(context.get('date_format')) if time.time() != datetime.time.min: format_ += ' ' + time_format(field) return time.strftime(format_) def format_date(): if not value: return '' format_ = date_format(context.get('date_format')) return value.strftime(format_) def format_time(): if not value: return '' return datetime.time.strftime(value, time_format(field)) def format_timedelta(): if not value: return '' return timedelta_format(value, context.get(field.get('converter'))) def format_many2one(): if value is None: return '' return value converts = { 'boolean': format_boolean, 'integer': format_integer, 'float': format_float, 'numeric': format_float, 'selection': format_selection, 'multiselection': format_selection, 'reference': format_reference, 'datetime': format_datetime, 'timestamp': format_datetime, 'date': format_date, 'time': format_time, 'timedelta': format_timedelta, 'many2one': format_many2one, } if isinstance(value, (list, tuple)): return ';'.join( format_value(field, x, context=context, _quote_empty=True) for x in value) return quote(converts.get(field['type'], lambda: value if value is not None else '')(), empty=_quote_empty) def complete_value(field, value): "Complete value for field" def complete_boolean(): if value is None: yield True yield False elif value: yield False else: yield True def complete_selection(): test_value = value if value is not None else '' if isinstance(value, list): test_value = value[-1] or '' test_value = test_value.strip('%') for svalue, test in field['selection']: if test.lower().startswith(test_value.lower()): if isinstance(value, list): yield value[:-1] + [svalue] else: yield svalue def complete_reference(): test_value = value if value is not None else '' if isinstance(value, list): test_value = value[-1] test_value = test_value.strip('%') for svalue, test in field['selection']: if test.lower().startswith(test_value.lower()): if isinstance(value, list): yield value[:-1] + [svalue] else: yield likify(svalue) def complete_datetime(): yield datetime.date.today() yield datetime.datetime.utcnow() def complete_date(): yield datetime.date.today() def complete_time(): yield datetime.datetime.now().time() completes = { 'boolean': complete_boolean, 'selection': complete_selection, 'multiselection': complete_selection, 'reference': complete_reference, 'datetime': complete_datetime, 'timestamp': complete_datetime, 'date': complete_date, 'time': complete_time, } return completes.get(field['type'], lambda: [])() def parenthesize(tokens): "Nest tokens according to parenthesis" for token in tokens: if token == '(': yield iter(list(parenthesize(tokens))) elif token == ')': break else: yield token def operatorize(tokens, operator='or'): "Convert operators" test = { 'or': ('|', ('|',)), 'and': ('&', ('&',)), }[operator] try: cur = next(tokens) while cur in test: cur = next(tokens) except StopIteration: return if isgenerator(cur): cur = operatorize(cur, operator) nex = None for nex in tokens: if isgenerator(nex): nex = operatorize(nex, operator) if nex in test: try: nex = next(tokens) while nex in test: nex = next(tokens) if isgenerator(nex): nex = operatorize(nex, operator) cur = iter([operator.upper(), cur, nex]) except StopIteration: if cur not in test: yield iter([operator.upper(), cur]) cur = None nex = None else: if cur not in test: yield cur cur = nex else: if nex is not None and nex not in test: yield nex elif cur is not None and cur not in test: yield cur class DomainParser(object): "A parser for domain" def __init__(self, fields, context=None): self.fields = OrderedDict() self.strings = OrderedDict() self.context = context def update_fields(fields, prefix='', string_prefix=''): for name, field in fields.items(): if not field.get('searchable', True) or name == 'rec_name': continue field = field.copy() fullname = '.'.join(filter(None, [prefix, name])) string = '.'.join( filter(None, [string_prefix, field['string']])) field['string'] = string field['name'] = fullname self.fields[fullname] = field self.strings[string.lower()] = field rfields = field.get('relation_fields') if rfields: update_fields(rfields, fullname, string) update_fields(fields) def parse(self, input_): "Return domain for the input string" try: tokens = udlex(input_) tokens = group_operator(tokens) tokens = parenthesize(tokens) tokens = self.group(tokens) tokens = operatorize(tokens, 'or') tokens = operatorize(tokens, 'and') tokens = self.parse_clause(tokens) return simplify(rlist(tokens)) except ValueError as exception: if str(exception) == 'No closing quotation': return self.parse(input_ + '"') def stringable(self, domain): def stringable_(clause): if not clause: return True if (( (clause[0] in ('AND', 'OR')) or isinstance(clause[0], (list, tuple))) and all(isinstance(c, (list, tuple)) for c in clause[1:])): return self.stringable(clause) name, _, value = clause[:3] if name.endswith('.rec_name'): name = name[:-len('.rec_name')] if name in self.fields: field = self.fields[name] if field['type'] in { 'many2one', 'one2one', 'one2many', 'many2many'}: types = (str, type(None)) if isinstance(value, (list, tuple)): return all(isinstance(v, types) for v in value) else: return isinstance(value, types) elif field['type'] == 'multiselection': return not value or isinstance(value, list) else: return True elif name == 'rec_name': return True return False if not domain: return True if domain[0] in ('AND', 'OR'): domain = domain[1:] return all(stringable_(clause) for clause in domain) def string(self, domain): "Return string for the domain" def string_(clause): if not clause: return '' if (not isinstance(clause[0], str) or clause[0] in ('AND', 'OR')): return '(%s)' % self.string(clause) name, operator, value = clause[:3] if name.endswith('.rec_name'): name = name[:-9] if name not in self.fields: if is_full_text(value): value = value[1:-1] return quote(value) field = self.fields[name] if len(clause) > 3: target = clause[3] else: target = None if 'ilike' in operator: if is_full_text(value): value = value[1:-1] elif not is_like(value): if operator == 'ilike': operator = '=' else: operator = '!' value = unescape(value) def_operator = default_operator(field) if def_operator == operator.strip(): operator = '' if value in OPERATORS: # As the value could be interpreted as an operator, # the default operator must be forced operator = '"" ' elif (def_operator in operator and ('not' in operator or '!' in operator)): operator = operator.rstrip(def_operator ).replace('not', '!').strip() if operator.endswith('in'): if isinstance(value, (list, tuple)) and len(value) == 1: if operator == 'not in': operator = '!=' else: operator = '=' else: if operator == 'not in': operator = '!' else: operator = '' formatted_value = format_value(field, value, target, self.context) if (operator in OPERATORS and field['type'] in ('char', 'text', 'selection') and value == ''): formatted_value = '""' return '%s: %s%s' % (quote(field['string']), operator, formatted_value) if not domain: return '' if domain[0] in ('AND', 'OR'): nary = ' ' if domain[0] == 'AND' else ' | ' domain = domain[1:] else: nary = ' ' return nary.join(string_(clause) for clause in domain) def completion(self, input_): "Return completion for the input string" domain = self.parse(input_) closing = 0 for i in range(1, len(input_)): if input_[-i] not in (')', ' '): break if input_[-i] == ')': closing += 1 ending, deep_ending = ending_clause(domain) deep = deep_ending - closing if deep: pslice = slice(-deep) else: pslice = slice(None) if self.string(domain)[pslice] != input_: yield self.string(domain)[pslice] complete = None if ending is not None and closing == 0: for complete in self.complete(ending): yield self.string(rlist( replace_ending_clause(domain, complete)))[pslice] if input_: if input_[-1] != ' ': return if len(input_) >= 2 and input_[-2] == ':': return for field in self.strings.values(): operator = default_operator(field) value = '' if 'ilike' in operator: value = likify(value) yield self.string(rlist(append_ending_clause(domain, (field['name'], operator, value), deep)))[pslice] def complete(self, clause): "Return all completion for the clause" if len(clause) == 1: name, = clause elif len(clause) == 3: name, operator, value = clause else: name, operator, value, target = clause if name.endswith('.rec_name'): name = name[:-9] value = target if name == 'rec_name': if operator == 'ilike': escaped = value.replace('%%', '__') if escaped.startswith('%') and escaped.endswith('%'): value = value[1:-1] elif '%' not in escaped: value = value.replace('%%', '%') operator = None name = value value = '' if not name: name = '' if (name.lower() not in self.strings and name not in self.fields): for field in self.strings.values(): if field['string'].lower().startswith(name.lower()): operator = default_operator(field) value = '' if 'ilike' in operator: value = likify(value) yield (field['name'], operator, value) return if name in self.fields: field = self.fields[name] else: field = self.strings[name.lower()] if not operator: operator = default_operator(field) value = '' if 'ilike' in operator: value = likify(value) yield (field['name'], operator, value) else: for comp in complete_value(field, value): yield (field['name'], operator, comp) def group(self, tokens): "Group tokens by clause" def _group(parts): try: i = parts.index(':') except ValueError: for part in parts: yield (part,) return for j in range(i): name = ' '.join(parts[j:i]) if name.lower() in self.strings: if parts[:j]: for part in parts[:j]: yield (part,) else: yield (None,) name = (name,) # empty string is also the default operator if (i + 1 < len(parts) and parts[i + 1] in OPERATORS + ('',)): name += (parts[i + 1],) i += 1 else: name += (None,) lvalue = [] while i + 2 < len(parts): if parts[i + 2] == ';': lvalue.append(parts[i + 1]) i += 2 else: break for part in _group(parts[i + 1:]): if name: if lvalue: if part[0] is not None: lvalue.append(part[0]) yield name + (lvalue,) else: yield name + part name = None else: yield part if name: if lvalue: yield name + (lvalue,) else: yield name + (None,) break parts = [] for token in tokens: if isgenerator(token): for group in _group(parts): if group != (None,): yield group parts = [] yield self.group(token) else: parts.append(token) for group in _group(parts): if group != (None,): yield group def parse_clause(self, tokens): "Parse clause" for clause in tokens: if isgenerator(clause): yield self.parse_clause(clause) elif clause in ('OR', 'AND'): yield clause else: if len(clause) == 1: yield ('rec_name', 'ilike', likify(clause[0])) else: name, operator, value = clause field = self.strings[name.lower()] field_name = field['name'] target = None if field['type'] == 'reference': target, value = split_target_value(field, value) if target: field_name += '.rec_name' elif field['type'] == 'multiselection': if value is not None and not isinstance(value, list): value = [value] if not operator: operator = default_operator(field) if (isinstance(value, list) and field['type'] != 'multiselection'): if operator == '!': operator = 'not in' else: operator = 'in' if operator == '!': operator = negate_operator(default_operator(field)) if value is None and operator.endswith('in'): if operator.startswith('not'): operator = '!=' else: operator = '=' if field['type'] in { 'integer', 'float', 'numeric', 'datetime', 'timestamp', 'date', 'time'}: if isinstance(value, str) and '..' in value: lvalue, rvalue = value.split('..', 1) lvalue = convert_value(field, lvalue, self.context) rvalue = convert_value(field, rvalue, self.context) yield iter([ (field_name, '>=', lvalue), (field_name, '<=', rvalue), ]) continue if isinstance(value, list): value = [convert_value(field, v, self.context) for v in value] if field['type'] in ('many2one', 'one2many', 'many2many', 'one2one'): field_name += '.rec_name' else: value = convert_value(field, value, self.context) if 'like' in operator: value = likify(value) if target: yield (field_name, operator, value, target) else: yield field_name, operator, value ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685598.0 tryton-7.0.24/tryton/common/entry_position.py0000644000175000017500000000043114517761236017553 0ustar00cedced# This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. def reset_position(entry): if entry.get_alignment() <= 0.5: entry.set_position(0) else: entry.set_position(-1) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685599.0 tryton-7.0.24/tryton/common/environment.py0000644000175000017500000000347014517761237017041 0ustar00cedced# This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. class EvalEnvironment(dict): def __init__(self, parent, eval_type='eval'): super(EvalEnvironment, self).__init__() self.parent = parent assert eval_type in ('eval', 'on_change') self.eval_type = eval_type def __getitem__(self, item): if item == 'id': return self.parent.id if item == '_parent_' + self.parent.parent_name and self.parent.parent: return EvalEnvironment(self.parent.parent, eval_type=self.eval_type) if self.eval_type == 'eval': return self.parent.get_eval()[item] else: return self.parent.group.fields[item].get_on_change_value( self.parent) def __getattr__(self, item): try: return self.__getitem__(item) except KeyError: raise AttributeError(item) def get(self, item, default=None): try: return self.__getitem__(item) except KeyError: pass return super(EvalEnvironment, self).get(item, default) def __bool__(self): return True def __str__(self): return str(self.parent) __repr__ = __str__ def __contains__(self, item): if item == 'id': return True if item == '_parent_' + self.parent.parent_name and self.parent.parent: return True if self.eval_type == 'eval': return item in self.parent.get_eval() else: return item in self.parent.group.fields def keys(self): if self.eval_type == 'eval': return self.parent.get_eval().keys() else: return self.parent.group.fields.keys() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685599.0 tryton-7.0.24/tryton/common/focus.py0000644000175000017500000000464714517761237015623 0ustar00cedced# This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. from functools import cmp_to_key from gi.repository import Gtk def get_invisible_ancestor(widget): if not widget.get_visible(): return widget if not widget.props.parent: return None return get_invisible_ancestor(widget.props.parent) def find_focused_child(widget): if widget.has_focus(): return widget if not hasattr(widget, 'get_children'): return None for child in widget.get_children(): focused_widget = find_focused_child(child) if focused_widget: return focused_widget def tab_compare(a, b): text_direction = Gtk.Widget.get_default_direction() a_allocation = a.get_allocation() b_allocation = b.get_allocation() y1 = a_allocation.y + a_allocation.height // 2 y2 = b_allocation.y + b_allocation.height // 2 if y1 == y2: x1 = a_allocation.x + a_allocation.width // 2 x2 = b_allocation.x + b_allocation.width // 2 if text_direction == Gtk.TextDirection.RTL: return (x2 > x1) - (x2 < x1) else: return (x1 > x2) - (x1 < x2) else: return (y1 > y2) - (y1 < y2) def get_focus_children(widget): if hasattr(widget, 'get_children'): result = sorted(widget.get_children(), key=cmp_to_key(tab_compare)) return result else: return [] def find_focusable_child(widget): if not widget.get_visible(): return None if (widget.get_can_focus() and (not isinstance(widget, Gtk.Entry) or widget.props.editable)): return widget for child in get_focus_children(widget): focusable = find_focusable_child(child) if focusable: return focusable def next_focus_widget(widget): if not widget.props.parent: return None focus_widget = find_focusable_child(widget.props.parent) if focus_widget: return focus_widget else: return next_focus_widget(widget.props.parent) def find_first_focus_widget(ancestor, widgets): "Return the widget from widgets which should have first the focus" if len(widgets) == 1: return widgets[0] for child in get_focus_children(ancestor): common = [w for w in widgets if w.is_ancestor(child)] if common: return find_first_focus_widget(child, common) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685599.0 tryton-7.0.24/tryton/common/htmltextbuffer.py0000644000175000017500000003410414517761237017536 0ustar00cedced# This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. import sys import xml.etree.ElementTree as ET from html.parser import HTMLParser from io import StringIO from xml.sax.saxutils import escape, unescape from gi.repository import Gdk, Gtk, Pango def guess_decode(bytes, errors='strict'): for encoding in [sys.getfilesystemencoding(), 'utf-8', 'utf-16', 'utf-32']: try: return bytes.decode(encoding) except UnicodeDecodeError: continue else: return bytes.decode('ascii', errors=errors) MIME = Gdk.Atom.intern('text/html', False) # Disable serialize/deserialize registration function because it does not work # on GTK-3, the "guint8 *data" is converted into a Gtk.TextIter use_serialize_func = False def _reverse_dict(dct): return {j: i for i, j in dct.items()} SIZE2SCALE = { '1': 1 / (1.2 * 1.2 * 1.2), '2': 1 / (1.2 * 1.2), '3': 1 / 1.2, '4': 1, '5': 1.2, '6': 1.2 * 1.2, '7': 1.2 * 1.2 * 1.2, } SCALE2SIZE = _reverse_dict(SIZE2SCALE) ALIGN2JUSTIFICATION = { 'left': Gtk.Justification.LEFT, 'center': Gtk.Justification.CENTER, 'right': Gtk.Justification.RIGHT, 'justify': Gtk.Justification.FILL, } JUSTIFICATION2ALIGN = _reverse_dict(ALIGN2JUSTIFICATION) FAMILIES = ['normal', 'sans', 'serif', 'monospace'] def gdk_to_hex(gdk_color): "Convert color to 2 digit hex" colors = [gdk_color.red, gdk_color.green, gdk_color.blue] return "#" + "".join(["%02x" % (color // 256) for color in colors]) def _markup(text): return '%s' % text def _strip_markup(text): return text[len(''):-len('')] def _strip_newline(text): return ''.join(text.splitlines()) class MarkupHTMLParse(HTMLParser): def __init__(self): HTMLParser.__init__(self) self.root = ET.Element('markup') self._tags = [self.root] self.head = False self.body = True def handle_starttag(self, tag, attrs): if tag in ['b', 'i', 'u', 'div', 'font']: el = ET.SubElement(self._tags[-1], tag) for key, value in attrs: el.set(key, value) self._tags.append(el) elif tag == 'br': ET.SubElement(self._tags[-1], tag) elif tag == 'head': self.head = True def handle_endtag(self, tag): if tag in ['b', 'i', 'u', 'div', 'font']: el = self._tags.pop() assert el.tag == tag elif tag == 'head': self.head = False elif tag == 'body': self.body = False def handle_data(self, data): if self.head or not self.body: return el = self._tags[-1] if len(el) == 0: if el.text is None: el.text = '' el.text += data else: child = el[-1] if child.tail is None: child.tail = '' child.tail += data def parse_markup(markup_text): 'Return plain text and a list of start, end TextTag' markup_text = StringIO(_markup(markup_text)) plain_text = '' tag_stack = [] tags = [] for event, element in ET.iterparse(markup_text, events=['start', 'end']): if element.tag == 'markup': if event == 'start' and element.text: plain_text += unescape(element.text) if event == 'end' and element.tail: plain_text += unescape(element.tail) continue if event == 'start': tag_stack.append((element, len(plain_text))) if element.text: plain_text += unescape(element.text) elif event == 'end': if element.tag == 'div': plain_text += '\n' assert tag_stack[-1][0] == element _, start = tag_stack.pop() end = len(plain_text) tags.append((start, end, element)) if element.tail: plain_text += unescape(element.tail) return plain_text, tags def normalize_markup(markup_text, method='html'): parser = MarkupHTMLParse() parser.feed(_strip_newline(markup_text)) root = parser.root parent_map = {c: p for p in root.iter() for c in p} def order(em): "Re-order alphabetically tags" if (len(em) == 1 and not em.text and em.tag not in ['div', 'markup']): child, = em if ((em.tag > child.tag or child.tag == 'div') and not child.tail): em.tag, child.tag = child.tag, em.tag em.attrib, child.attrib = child.attrib, em.attrib if em in parent_map: order(parent_map[em]) # Add missing div for the first line if len(root) > 0 and root[0].tag != 'div': div = ET.Element('div') for em in list(root): if em.tag != 'div': root.remove(em) div.append(em) else: break root.insert(0, div) for em in root.iter(): while em.text is None and len(em) == 1: dup, = em if dup.tag == em.tag and dup.tail is None: em.attrib.update(dup.attrib) em.remove(dup) em.extend(dup) em.text = dup.text else: break order(em) # Add missing br to empty lines for em in root.findall('div'): if em.text is None and len(em) == 0: em.append(ET.Element('br')) # TODO order attributes return _strip_markup( ET.tostring(root, encoding='utf-8', method=method).decode('utf-8')) def find_list_delta(old, new): added = [e for e in new if e not in old] removed = [e for e in reversed(old) if e not in new] return added, removed def serialize(register, content, start, end, data): text = '' if start.compare(end) == 0: return text iter_ = start while True: # Loop over line per line to get a div per line tags = [] active_tags = [] end_line = iter_.copy() if not end_line.ends_line(): end_line.forward_to_line_end() if end_line.compare(end) > 0: end_line = end # Open div of the line align = None for tag in iter_.get_tags(): if tag.props.justification_set: align = JUSTIFICATION2ALIGN[tag.props.justification] if align: text += '
' % align else: text += '
' while True: new_tags = iter_.get_tags() added, removed = find_list_delta(tags, new_tags) for tag in removed: if tag not in active_tags: continue # close all open tags after this one while active_tags: atag = active_tags.pop() text += _get_html(atag, close=True) if atag == tag: break added.insert(0, atag) for tag in added: text += _get_html(tag) active_tags.append(tag) tags = new_tags old_iter = iter_.copy() # Move to next tag toggle while True: iter_.forward_char() if iter_.compare(end) == 0: break if iter_.toggles_tag(): break # Might have moved too far if iter_.compare(end_line) > 0: iter_ = end_line text += escape(old_iter.get_text(iter_)) if iter_.compare(end_line) == 0: break # close any open tags for tag in reversed(active_tags): text += _get_html(tag, close=True) # Close the div of the line text += '
' iter_.forward_char() if iter_.compare(end) >= 0: break return normalize_markup(text) def deserialize(register, content, iter_, text, create_tags, data): if not isinstance(text, str): text = guess_decode(text, errors='replace') text, tags = parse_markup(normalize_markup(text, method='xml')) offset = iter_.get_offset() content.insert(iter_, text) def sort_key(tag): start, end, element = tag return start, -end, element.tag for start, end, element in sorted(tags, key=sort_key): for tag in _get_tags(content, element): istart = content.get_iter_at_offset(start + offset) iend = content.get_iter_at_offset(end + offset) content.apply_tag(tag, istart, iend) return True def setup_tags(text_buffer): for name, props in reversed(_TAGS): text_buffer.create_tag(name, **props) _TAGS = [ ('bold', {'weight': Pango.Weight.BOLD}), ('italic', {'style': Pango.Style.ITALIC}), ('underline', {'underline': Pango.Underline.SINGLE}), ] _TAGS.extend([('family %s' % family, {'family': family}) for family in FAMILIES]) _TAGS.extend([('size %s' % size, {'scale': scale}) for size, scale in SIZE2SCALE.items()]) _TAGS.extend([('justification %s' % align, {'justification': justification}) for align, justification in ALIGN2JUSTIFICATION.items()]) def register_foreground(text_buffer, color): name = 'foreground %s' % color.to_string() tag_table = text_buffer.get_tag_table() tag = tag_table.lookup(name) if not tag: tag = text_buffer.create_tag(name, foreground_rgba=color) return tag def _get_tags(content, element): 'Return tag for the element' tag_table = content.get_tag_table() if element.tag == 'b': yield tag_table.lookup('bold') elif element.tag == 'i': yield tag_table.lookup('italic') elif element.tag == 'u': yield tag_table.lookup('underline') if 'face' in element.attrib: tag = tag_table.lookup('family %s' % element.attrib['face']) if tag: yield tag size = element.attrib.get('size') if size in SIZE2SCALE: yield tag_table.lookup('size %s' % size) if 'color' in element.attrib: color = Gdk.RGBA() color.parse(element.attrib['color']) yield register_foreground(content, color) align = element.attrib.get('align') if align in ALIGN2JUSTIFICATION: yield tag_table.lookup('justification %s' % align) # TODO style background-color def _get_html(texttag, close=False, div_only=False): 'Return the html tag' # div alignment is managed at line level tags = [] attrib = {} font = attrib['font'] = {} if texttag.props.weight_set and texttag.props.weight == Pango.Weight.BOLD: tags.append('b') if texttag.props.style_set and texttag.props.style == Pango.Style.ITALIC: tags.append('i') if (texttag.props.underline_set and texttag.props.underline == Pango.Underline.SINGLE): tags.append('u') if texttag.props.family_set and texttag.props.family: font['face'] = texttag.props.family if texttag.props.scale_set and texttag.props.scale != 1: font['size'] = SCALE2SIZE[texttag.props.scale] if (texttag.props.foreground_set and texttag.props.foreground_gdk != Gdk.Color(0, 0, 0)): font['color'] = gdk_to_hex(texttag.props.foreground_gdk) # TODO style background-color if font: tags.append('font') if close: return ''.join('' % t for t in tags) return ''.join('<%s %s>' % (t, ' '.join('%s="%s"' % (a, v) for a, v in attrib.get(t, {}).items())) for t in tags) def get_tags(content, start, end): 'Get all tags' iter_ = start.copy() tags = set() while True: tags.update(iter_.get_tags()) iter_.forward_char() if iter_.compare(end) > 0: iter_ = end if iter_.compare(end) == 0: break tags.update(iter_.get_tags()) return tags def remove_tags(content, start, end, *names): 'Remove all tags starting by names' tags = get_tags(content, start, end) for tag in sorted(tags, key=lambda t: t.get_priority()): for name in names: if tag.props.name and tag.props.name.startswith(name): content.remove_tag(tag, start, end) if __name__ == '__main__': html = '''Bold Italic Underline

Center
Sans6red
Title
''' # noqa: E501 win = Gtk.Window() win.set_title('HTMLTextBuffer') def cb(window, event): if use_serialize_func: print(text_buffer.serialize( text_buffer, MIME, text_buffer.get_start_iter(), text_buffer.get_end_iter())) else: print(serialize( text_buffer, text_buffer, text_buffer.get_start_iter(), text_buffer.get_end_iter(), None)) Gtk.main_quit() win.connect('delete-event', cb) vbox = Gtk.VBox() win.add(vbox) text_buffer = Gtk.TextBuffer() text_view = Gtk.TextView() text_view.set_buffer(text_buffer) vbox.pack_start(text_view, expand=True, fill=True, padding=0) setup_tags(text_buffer) text_buffer.register_serialize_format(str(MIME), serialize, None) text_buffer.register_deserialize_format(str(MIME), deserialize, None) if use_serialize_func: text_buffer.deserialize( text_buffer, MIME, text_buffer.get_start_iter(), html) result = text_buffer.serialize( text_buffer, MIME, text_buffer.get_start_iter(), text_buffer.get_end_iter()) else: deserialize( text_buffer, text_buffer, text_buffer.get_start_iter(), html, text_buffer.deserialize_get_can_create_tags(MIME), None) result = serialize( text_buffer, text_buffer, text_buffer.get_start_iter(), text_buffer.get_end_iter(), None) assert normalize_markup(html) == result, (normalize_markup(html), result) win.show_all() Gtk.main() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1704711243.0 tryton-7.0.24/tryton/common/number_entry.py0000644000175000017500000000707414546752113017205 0ustar00cedced# This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. import gettext import locale from decimal import Decimal, InvalidOperation from gi.repository import Gdk, GObject, Gtk __all__ = ['NumberEntry'] _ = gettext.gettext class NumberEntry(Gtk.Entry, Gtk.Editable): # Override Editable to avoid modify the base implementation of Entry __gtype_name__ = 'NumberEntry' __digits = None def __init__(self, *args, **kwargs): self.monetary = kwargs.pop('monetary', False) self.convert = kwargs.pop('convert', float) super().__init__(*args, **kwargs) self.set_alignment(1.0) self.connect('key-press-event', self.__class__.__key_press_event) @GObject.Property( default=None, nick=_("Digits"), blurb=_("The number of decimal")) def digits(self): return self.__digits @digits.setter def digits(self, value): self.__digits = value @GObject.Property def value(self): text = self.get_text() if text: try: return self.convert(locale.delocalize(text, self.monetary)) except ValueError: pass return None @property def __decimal_point(self): return locale.localeconv()[ self.monetary and 'mon_decimal_point' or 'decimal_point'] @property def __thousands_sep(self): return locale.localeconv()[ self.monetary and 'mon_thousands_sep' or 'thousands_sep'] # XXX: Override vfunc because position is inout # https://gitlab.gnome.org/GNOME/pygobject/issues/12 def do_insert_text(self, new_text, length, position): buffer_ = self.get_buffer() text = self.get_buffer().get_text() text = text[:position] + new_text + text[position:] value = None if text not in ['-', self.__decimal_point, self.__thousands_sep]: try: value = Decimal(locale.delocalize(text, self.monetary)) except (ValueError, InvalidOperation): return position try: if (value and self.__digits is not None and (round(value, self.__digits) != value or value.as_tuple().exponent < -self.__digits)): return position except InvalidOperation: return position length = len(new_text.encode('utf-8')) buffer_.insert_text(position, new_text, length) return position + length def __key_press_event(self, event): for name in ['KP_Decimal', 'KP_Separator']: if event.keyval == Gdk.keyval_from_name(name): text = self.__decimal_point if self.get_selection_bounds(): self.delete_text(*self.get_selection_bounds()) self.do_insert_text( text, len(text), self.props.cursor_position) self.set_position(self.props.cursor_position + len(text)) return True GObject.type_register(NumberEntry) if __name__ == '__main__': win = Gtk.Window() win.connect('delete-event', Gtk.main_quit) vbox = Gtk.VBox() e = NumberEntry() vbox.pack_start(NumberEntry(), expand=False, fill=False, padding=0) vbox.pack_start(NumberEntry(digits=2), expand=False, fill=False, padding=0) vbox.pack_start(NumberEntry(digits=0), expand=False, fill=False, padding=0) vbox.pack_start( NumberEntry(digits=-2), expand=False, fill=False, padding=0) win.add(vbox) win.show_all() Gtk.main() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685599.0 tryton-7.0.24/tryton/common/popup_menu.py0000644000175000017500000001322314517761237016661 0ustar00cedced# This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. import gettext from gi.repository import Gdk, Gtk from tryton.action import Action from tryton.common import RPCException, RPCExecute from tryton.common.common import selection from tryton.gui.window import Window from tryton.gui.window.attachment import Attachment from tryton.gui.window.email_ import Email from tryton.gui.window.log import Log from tryton.gui.window.note import Note from tryton.gui.window.view_form.screen import Screen _ = gettext.gettext def populate(menu, model, record, title='', field=None, context=None): ''' Fill menu with the actions of model for the record. If title is filled, the actions will be put in a submenu. ''' if record is None: return elif isinstance(record, int): if record < 0: return elif record.id < 0: return def load(record): if isinstance(record, int): screen = Screen(model, context=context) screen.load([record]) record = screen.group.get(record) return record def id_(record): if not isinstance(record, int): return record.id return record def activate(menuitem, action, atype): rec = load(record) data = { 'model': model, 'id': rec.id, 'ids': [rec.id], } event = Gtk.get_current_event() allow_similar = False if (event.state & Gdk.ModifierType.CONTROL_MASK or event.state & Gdk.ModifierType.MOD1_MASK): allow_similar = True with Window(hide_current=True, allow_similar=allow_similar): Action.execute(action, data, context=rec.get_context()) def log(menuitem): Log(load(record)) def attachment(menuitem): Attachment(load(record), None) def note(menuitem): Note(load(record), None) def is_report(action): return action['type'] == 'ir.action.report' def email(menuitem, toolbar): rec = load(record) prints = filter(is_report, toolbar['print']) emails = {e['name']: e['id'] for e in toolbar['emails']} template = selection(_("Template"), emails, alwaysask=True) if template: template = template[1] Email( '%s: %s' % (title, rec.rec_name()), rec, prints, template=template) def edit(menuitem): with Window(hide_current=True, allow_similar=True): Window.create(model, view_ids=field.attrs.get('view_ids', '').split(','), res_id=id_(record), mode=['form'], name=field.attrs.get('string'), context=context) if title: if len(menu): menu.append(Gtk.SeparatorMenuItem()) title_item = Gtk.MenuItem(label=title) menu.append(title_item) submenu = Gtk.Menu() title_item.set_submenu(submenu) action_menu = submenu else: action_menu = menu if len(action_menu): action_menu.append(Gtk.SeparatorMenuItem()) if field: edit_item = Gtk.MenuItem(label=_('Edit...')) edit_item.connect('activate', edit) action_menu.append(edit_item) action_menu.append(Gtk.SeparatorMenuItem()) log_item = Gtk.MenuItem(label=_("View Logs...")) action_menu.append(log_item) log_item.connect('activate', log) attachment_item = Gtk.MenuItem(label=_('Attachments...')) action_menu.append(attachment_item) attachment_item.connect('activate', attachment) note_item = Gtk.MenuItem(label=_('Notes...')) action_menu.append(note_item) note_item.connect('activate', note) try: toolbar = RPCExecute('model', model, 'view_toolbar_get') except RPCException: pass else: for atype, icon, label, flavor in ( ('action', 'tryton-launch', _('Actions...'), None), ('relate', 'tryton-link', _('Relate...'), None), ('print', 'tryton-open', _('Report...'), 'open'), ('print', 'tryton-print', _('Print...'), 'print'), ): if len(action_menu): action_menu.append(Gtk.SeparatorMenuItem()) title_item = Gtk.MenuItem(label=label) action_menu.append(title_item) if not toolbar[atype]: title_item.set_sensitive(False) continue submenu = Gtk.Menu() title_item.set_submenu(submenu) for action in toolbar[atype]: action = action.copy() item = Gtk.MenuItem(label=action['name']) submenu.append(item) if flavor == 'print': action['direct_print'] = True item.connect('activate', activate, action, atype) menu.show_all() email_item = Gtk.MenuItem(label=_('E-Mail...')) action_menu.append(email_item) email_item.connect('activate', email, toolbar) menu.show_all() def popup(menu, widget): def menu_position(menu, x, y, user_data): widget_allocation = widget.get_allocation() x, y = widget.get_window().get_root_coords( widget_allocation.x, widget_allocation.y) return (x, y + widget_allocation.height, False) menu.show_all() if hasattr(menu, 'popup_at_widget'): menu.popup_at_widget( widget, Gdk.Gravity.SOUTH_WEST, Gdk.Gravity.NORTH_WEST, Gtk.get_current_event()) else: menu.popup( None, None, menu_position, None, 0, Gtk.get_current_event_time()) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685599.0 tryton-7.0.24/tryton/common/richtext.py0000644000175000017500000002575714517761237016343 0ustar00cedced# This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. import gettext from contextlib import contextmanager from gi.repository import Gdk, Gtk from tryton.common import IconFactory from tryton.common.htmltextbuffer import ( FAMILIES, MIME, SIZE2SCALE, deserialize, register_foreground, remove_tags, serialize, setup_tags, use_serialize_func) _ = gettext.gettext SIZES = sorted(SIZE2SCALE.keys()) def register_format(textview): buffer_ = textview.get_buffer() setup_tags(buffer_) buffer_.register_serialize_format(str(MIME), serialize, None) buffer_.register_deserialize_format(str(MIME), deserialize, None) def set_content(textview, content): with disable_text_style(textview): buffer_ = textview.get_buffer() start = buffer_.get_start_iter() end = buffer_.get_end_iter() buffer_.delete(start, end) if use_serialize_func: buffer_.deserialize(buffer_, MIME, start, content) else: deserialize( buffer_, buffer_, start, content, buffer_.deserialize_get_can_create_tags(MIME), None) def get_content(textview): buffer_ = textview.get_buffer() start = buffer_.get_start_iter() end = buffer_.get_end_iter() if use_serialize_func: return buffer_.serialize(buffer_, MIME, start, end) else: return serialize(buffer_, buffer_, start, end, None) def add_toolbar(textview): toolbar = Gtk.Toolbar() tag_widgets = {} colors = {} for icon, label in [ ('bold', _("Bold")), ('italic', _("Italic")), ('underline', _("Underline")), ]: button = Gtk.ToggleToolButton() button.set_icon_widget(IconFactory.get_image( 'tryton-format-%s' % icon, Gtk.IconSize.SMALL_TOOLBAR)) button.set_label(label) button.connect('toggled', _toggle_props, icon, textview) toolbar.insert(button, -1) tag_widgets[icon] = button toolbar.insert(Gtk.SeparatorToolItem(), -1) for name, options, active in [ ('family', FAMILIES, FAMILIES.index('normal')), ('size', SIZES, SIZES.index('4')), ]: combobox = Gtk.ComboBoxText() for option in options: combobox.append_text(option) combobox.set_active(active) combobox.set_focus_on_click(False) combobox.connect('changed', _change_props, name, textview) tool = Gtk.ToolItem() tool.add(combobox) toolbar.insert(tool, -1) tag_widgets[name] = combobox toolbar.insert(Gtk.SeparatorToolItem(), -1) button = None for name, label in [ ('left', _("Align Left")), ('center', _("Align Center")), ('right', _("Align Right")), ('justify', _("Justify")), ]: icon = 'tryton-format-align-%s' % name button = Gtk.RadioToolButton.new_from_widget(button) button.set_icon_widget(IconFactory.get_image( icon, Gtk.IconSize.SMALL_TOOLBAR)) button.set_active(icon == 'left') button.set_label(label) button.connect( 'toggled', _toggle_justification, name, textview) toolbar.insert(button, -1) tag_widgets[name] = button toolbar.insert(Gtk.SeparatorToolItem(), -1) for icon, label in [ ('foreground', _("Foreground Color")), # TODO ('background', _('Background')), ]: button = Gtk.ToolButton() if icon == 'foreground': button.set_icon_widget(IconFactory.get_image( 'tryton-format-color-text', Gtk.IconSize.SMALL_TOOLBAR)) button.set_label(label) button.connect('clicked', _toggle_color, icon, textview, colors) toolbar.insert(button, -1) tag_widgets[icon] = button buffer_ = textview.get_buffer() buffer_.connect_after( 'insert-text', _insert_text_style, tag_widgets) textview.connect_after( 'move-cursor', lambda *a: _detect_style(textview, tag_widgets, colors)) textview.connect_after( 'button-release-event', lambda *a: _detect_style(textview, tag_widgets, colors)) return toolbar @contextmanager def disable_text_style(textview): buffer_ = textview.get_buffer() try: buffer_.handler_block_by_func(_insert_text_style) except TypeError: pass yield try: buffer_.handler_unblock_by_func(_insert_text_style) except TypeError: pass def _toggle_props(toggle, name, textview): buffer_ = textview.get_buffer() try: start, end = buffer_.get_selection_bounds() except ValueError: return _apply_tool(buffer_, name, toggle, start, end) def _change_props(combobox, name, textview): buffer_ = textview.get_buffer() try: start, end = buffer_.get_selection_bounds() except ValueError: return _apply_tool(buffer_, name, combobox, start, end) def _toggle_justification(button, name, textview): buffer_ = textview.get_buffer() try: start, end = buffer_.get_selection_bounds() except ValueError: insert = buffer_.get_insert() start = buffer_.get_iter_at_mark(insert) end = start.copy() start.set_line_offset(0) if not end.ends_line(): end.forward_to_line_end() _apply_tool(buffer_, name, button, start, end) def _toggle_color(button, name, textview, colors): buffer_ = textview.get_buffer() insert = buffer_.get_insert() try: start, end = buffer_.get_selection_bounds() except ValueError: start = end = None else: # Use offset position to preserve across buffer_ modification start = start.get_offset() end = end.get_offset() dialog = Gtk.ColorChooserDialog( title=_('Select a color'), transient_for=textview.get_toplevel(), use_alpha=False) color = Gdk.RGBA() if name in colors: color.parse(colors[name]) dialog.set_rgba(color) if dialog.run() == Gtk.ResponseType.OK: color = dialog.get_rgba() if start is not None and end is not None: start = buffer_.get_iter_at_offset(start) end = buffer_.get_iter_at_offset(end) tag = register_foreground(buffer_, color) remove_tags(buffer_, start, end, name) buffer_.apply_tag(tag, start, end) dialog.destroy() buffer_.place_cursor(buffer_.get_iter_at_mark(insert)) def _apply_tool(buffer_, name, tool, start, end): # First test RadioToolButton as they inherit from ToggleToolButton if isinstance(tool, Gtk.RadioToolButton): name = 'justification %s' % name if not tool.get_active(): remove_tags(buffer_, start, end, name) else: remove_tags(buffer_, start, end, 'justification') buffer_.apply_tag_by_name(name, start, end) elif isinstance(tool, Gtk.ToggleToolButton): if tool.get_active(): buffer_.apply_tag_by_name(name, start, end) else: buffer_.remove_tag_by_name(name, start, end) elif isinstance(tool, Gtk.ComboBoxText): value = tool.get_active_text() remove_tags(buffer_, start, end, name) name = '%s %s' % (name, value) buffer_.apply_tag_by_name(name, start, end) def _insert_text_style(buffer_, iter_, text, length, tag_widgets): # Text is already inserted so iter_ points to the end start = iter_.copy() start.backward_chars(length) end = iter_.copy() # Apply tags activated from the toolbar for name, widget in tag_widgets.items(): _apply_tool(buffer_, name, widget, start, end) def _detect_style(textview, tag_widgets, colors): buffer_ = textview.get_buffer() try: start, end = buffer_.get_selection_bounds() except ValueError: start = end = buffer_.get_iter_at_mark( buffer_.get_insert()) def toggle_button(name, values): try: value, = values except ValueError: value = False button = tag_widgets[name] button.handler_block_by_func(_toggle_props) button.set_active(value) button.handler_unblock_by_func(_toggle_props) def set_combobox(name, indexes): try: index, = indexes except ValueError: index = -1 combobox = tag_widgets[name] combobox.handler_block_by_func(_change_props) combobox.set_active(index) combobox.handler_unblock_by_func(_change_props) def toggle_justification(names, value): if len(names) != 1: value = False for name in names: button = tag_widgets[name] button.handler_block_by_func(_toggle_justification) button.set_active(value) button.handler_unblock_by_func(_toggle_justification) bolds, italics, underlines = set(), set(), set() families, sizes, justifications = set(), set(), set() colors['foreground'] = 'black' iter_ = start.copy() while True: bold, italic, underline = False, False, False family = FAMILIES.index('normal') size = SIZES.index('4') justification = 'left' for tag in iter_.get_tags(): if not tag.props.name: continue elif tag.props.name == 'bold': bold = True elif tag.props.name == 'italic': italic = True elif tag.props.name == 'underline': underline = True elif tag.props.name.startswith('family'): _, family = tag.props.name.split() family = FAMILIES.index(family) elif tag.props.name.startswith('size'): _, size = tag.props.name.split() size = SIZES.index(size) elif tag.props.name.startswith('justification'): _, justification = tag.props.name.split() elif tag.props.name.startswith('foreground'): _, colors['foreground'] = tag.props.name.split() bolds.add(bold) italics.add(italic) underlines.add(underline) families.add(family) sizes.add(size) justifications.add(justification) iter_.forward_char() if iter_.compare(end) > 0: iter_ = end if iter_.compare(end) == 0: break for name, values in [ ('bold', bolds), ('italic', italics), ('underline', underlines)]: toggle_button(name, values) set_combobox('family', families) set_combobox('size', sizes) toggle_justification(justifications, True) if __name__ == '__main__': win = Gtk.Window() box = Gtk.VBox() win.add(box) textview = Gtk.TextView() register_format(textview) toolbar = add_toolbar(textview) box.pack_start(toolbar, expand=False, fill=True, padding=0) box.pack_start(textview, expand=True, fill=True, padding=0) win.show_all() Gtk.main() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1740502122.0 tryton-7.0.24/tryton/common/selection.py0000644000175000017500000002033614757372152016462 0ustar00cedced# This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. import math import operator from gi.repository import Gdk, GLib, GObject, Gtk from tryton.common import RPCException, RPCExecute, eval_domain class SelectionMixin(object): def __init__(self, *args, **kwargs): super(SelectionMixin, self).__init__(*args, **kwargs) self.nullable_widget = True self.selection = None self.inactive_selection = [] self._last_domain = None self._values2selection = {} self._domain_cache = {} def init_selection(self, value=None): if value is None: value = dict((k, None) for k in self.attrs.get('selection_change_with') or []) key = freeze_value(value) selection = self.attrs.get('selection', [])[:] help_ = self.attrs.get('help_selection', {}) if (not isinstance(selection, (list, tuple)) and key not in self._values2selection): try: if self.attrs.get('selection_change_with'): selection = RPCExecute('model', self.model_name, selection, value) else: selection = RPCExecute('model', self.model_name, selection) except RPCException: selection = [] self._values2selection[key] = selection elif key in self._values2selection: selection = self._values2selection[key] if self.attrs.get('sort', True): selection.sort(key=operator.itemgetter(1)) self.selection = selection[:] self.help = help_ self.inactive_selection = [] def update_selection(self, record, field): if not field: return if not self.selection: self.init_selection() domain = field.domain_get(record) if 'relation' not in self.attrs: change_with = self.attrs.get('selection_change_with') or [] value = record._get_on_change_args(change_with) value.pop('id', None) self.init_selection(value) self.filter_selection(domain, record, field) else: context = field.get_context(record) domain_cache_key = (freeze_value(domain), freeze_value(context)) if domain_cache_key in self._domain_cache: self.selection = self._domain_cache[domain_cache_key] self._last_domain = (domain, context) if (domain, context) == self._last_domain: return fields = ['rec_name'] help_field = self.attrs.get('help_field') if help_field: fields.append(help_field) try: result = RPCExecute('model', self.attrs['relation'], 'search_read', domain, 0, None, None, fields, context=context, process_exception=False) except RPCException: result = False if isinstance(result, list): selection = [(x['id'], x['rec_name']) for x in result] if self.nullable_widget: selection.append((None, '')) if help_field: help_ = {x['id']: x[help_field] for x in result} else: help_ = {} self._last_domain = (domain, context) self._domain_cache[domain_cache_key] = selection else: selection = [] if self.nullable_widget: selection.append((None, '')) help_ = {} self._last_domain = None self.selection = selection[:] self.help = help_ self.inactive_selection = [] def filter_selection(self, domain, record, field): if not domain: return def _value_evaluator(value): return eval_domain(domain, { self.field_name: value[0], }) def _model_evaluator(allowed_models): def test(value): return value[0] in allowed_models or not allowed_models return test type_ = field.attrs['type'] if type_ == 'reference': allowed_models = field.get_models(record) evaluator = _model_evaluator(allowed_models) elif type_ == 'multiselection': return else: evaluator = _value_evaluator self.selection = list(filter(evaluator, self.selection)) def get_inactive_selection(self, value): if 'relation' not in self.attrs: return '' if value is None: return '' for val, text in self.inactive_selection: if str(val) == str(value): return text else: try: result, = RPCExecute('model', self.attrs['relation'], 'read', [value], ['rec_name']) self.inactive_selection.append((result['id'], result['rec_name'])) return result['rec_name'] except RPCException: return '' def selection_shortcuts(entry): def key_press(widget, event): if (event.type == Gdk.EventType.KEY_PRESS and event.state & Gdk.ModifierType.CONTROL_MASK and event.keyval == Gdk.KEY_space): widget.popup() entry.connect('key_press_event', key_press) return entry def freeze_value(value): if isinstance(value, dict): return tuple(sorted((k, freeze_value(v)) for k, v in value.items())) elif isinstance(value, (list, set, tuple)): return tuple(freeze_value(v) for v in value) else: return value class PopdownMixin(object): def set_popdown(self, selection, entry): child = entry.get_child() if not child: # entry is destroyed return model, lengths = self.get_popdown_model(selection) entry.set_model(model) entry.set_entry_text_column(0) completion = Gtk.EntryCompletion() completion.set_inline_selection(True) completion.set_model(model) child.set_completion(completion) if lengths: pop = sorted(lengths, reverse=True) average = sum(pop) / len(pop) deviation = int( math.sqrt(sum((x - average) ** 2 for x in pop) / len(pop))) width = max(next( (x for x in pop if abs(x - average) < (deviation * 2)), 10), 10) else: width = 10 child.set_width_chars(width) if lengths: child.set_max_length(max(lengths)) completion.set_text_column(0) completion.connect('match-selected', self.match_selected, entry) def get_popdown_model(self, selection): model = Gtk.ListStore(GObject.TYPE_STRING, GObject.TYPE_PYOBJECT) lengths = [] for (value, name) in selection: name = str(name) model.append((name, value)) lengths.append(len(name)) return model, lengths def match_selected(self, completion, model, iter_, entry): value, = model.get(iter_, 1) model = entry.get_model() for i, values in enumerate(model): if values[1] == value: GLib.idle_add(entry.set_active, i) break def get_popdown_value(self, entry, index=1): active = entry.get_active() if active < 0: return None else: model = entry.get_model() return model[active][index] def get_popdown_text(self, entry): return self.get_popdown_value(entry, index=0) def set_popdown_value(self, entry, value): active = -1 model = entry.get_model() for i, selection in enumerate(model): if selection[1] == value: active = i break else: if value: return False entry.set_active(active) if active == -1: # When setting no item GTK doesn't clear the entry entry.get_child().set_text('') return True ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685598.0 tryton-7.0.24/tryton/common/timedelta.py0000644000175000017500000000615414517761236016446 0ustar00cedced# This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. import datetime import gettext import locale from collections import OrderedDict __all__ = ['format', 'parse'] _ = gettext.gettext DEFAULT_CONVERTER = { 's': 1, } DEFAULT_CONVERTER['m'] = DEFAULT_CONVERTER['s'] * 60 DEFAULT_CONVERTER['h'] = DEFAULT_CONVERTER['m'] * 60 DEFAULT_CONVERTER['d'] = DEFAULT_CONVERTER['h'] * 24 DEFAULT_CONVERTER['w'] = DEFAULT_CONVERTER['d'] * 7 DEFAULT_CONVERTER['M'] = DEFAULT_CONVERTER['d'] * 30 DEFAULT_CONVERTER['Y'] = DEFAULT_CONVERTER['d'] * 365 def _get_separators(): return OrderedDict([ ('Y', _('Y')), ('M', _('M')), ('w', _('w')), ('d', _('d')), ('h', _('h')), ('m', _('m')), ('s', _('s')), ]) def format(value, converter=None): 'Convert timedelta to text' if value is None: return '' if not converter: converter = DEFAULT_CONVERTER text = [] value = value.total_seconds() sign = '' if value < 0: sign = '-' value = abs(value) converter = [(k, converter.get(k)) for k in _get_separators()] values = [] for k, v in converter: if v: part = value // v value -= part * v values.append(part) else: values.append(0) for (k, _), v in zip(converter[:-3], values): if v: text.append( locale.format_string('%d', v, True) + _get_separators()[k]) if any(values[-3:]) or not text: time = '%02d:%02d' % tuple(values[-3:-1]) if values[-1] or value: time += ':%02d' % values[-1] text.append(time) text = sign + ' '.join(text) if value: if not any(values[-3:]): # Add space if no time text += ' ' text += locale.format_string('%.6f', value)[1:] return text def parse(text, converter=None): if not text: return if not converter: converter = DEFAULT_CONVERTER for separator in list(_get_separators().values()): text = text.replace(separator, separator + ' ') seconds = 0 for part in text.split(): if ':' in part: for t, v in zip(part.split(':'), [converter['h'], converter['m'], converter['s']]): try: seconds += abs(locale.atof(t)) * v except ValueError: pass else: for key, separator in list(_get_separators().items()): if part.endswith(separator): part = part[:-len(separator)] try: seconds += abs(locale.atof(part)) * converter[key] except ValueError: pass break else: try: seconds += abs(locale.atof(part)) except ValueError: pass if '-' in text: seconds *= -1 return datetime.timedelta(seconds=seconds) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685599.0 tryton-7.0.24/tryton/common/underline.py0000644000175000017500000000131214517761237016453 0ustar00cedced# This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. from gi.repository import Gdk def find_hardware_keycode(string): keymap = Gdk.Keymap.get_for_display(Gdk.Display.get_default()) for i, c in enumerate(string): found, keys = keymap.get_entries_for_keyval( Gdk.unicode_to_keyval(ord(c))) if found: return i return -1 def set_underline(label): "Set underscore for mnemonic accelerator" label = label.replace('_', '__') position = find_hardware_keycode(label) if position >= 0: label = label[:position] + '_' + label[position:] return label ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685599.0 tryton-7.0.24/tryton/common/widget_style.py0000644000175000017500000000051714517761237017177 0ustar00cedced# This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. def widget_class(widget, name, value): style_context = widget.get_style_context() if value: style_context.add_class(name) else: style_context.remove_class(name) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1725720314.0 tryton-7.0.24/tryton/config.py0000644000175000017500000002200314667063372014444 0ustar00cedced# This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. import configparser import gettext import locale import logging import optparse import os import shutil import sys import tempfile from gi.repository import GdkPixbuf from tryton import __version__ logger = logging.getLogger(__name__) _ = gettext.gettext def _reverse_series_iterator(starting_version): major, minor = map(int, starting_version.split('.')) while major >= 0 and minor >= 0: yield f"{major}.{minor}" if minor == 0: major -= 1 minor = 8 else: minor -= 2 if not minor % 2 else 1 def copy_previous_configuration(config_element): current_version = __version__.rsplit('.', 1)[0] config_dir = get_config_root() for version in _reverse_series_iterator(current_version): config_path = os.path.join(config_dir, version, config_element) if version == current_version and os.path.exists(config_path): break elif os.path.exists(config_path): if os.path.isfile(config_path): shutil.copy(config_path, get_config_dir()) elif os.path.isdir(config_path): shutil.copytree( config_path, os.path.join(get_config_dir(), config_element)) break def get_config_root(): if os.name == 'nt': appdata = os.environ['APPDATA'] if not isinstance(appdata, str): appdata = str(appdata, sys.getfilesystemencoding()) config_path = os.path.join(appdata, '.config') else: config_path = os.path.expanduser(os.getenv( 'XDG_CONFIG_HOME', os.path.join('~', '.config'))) return os.path.join(config_path, 'tryton') def get_config_dir(): return os.path.join(get_config_root(), __version__.rsplit('.', 1)[0]) os.makedirs(get_config_dir(), mode=0o700, exist_ok=True) class ConfigManager(object): "Config manager" def __init__(self): short_version = '.'.join(__version__.split('.', 2)[:2]) demo_server = 'demo%s.tryton.org' % short_version demo_database = 'demo%s' % short_version self.defaults = { 'login.profile': demo_server, 'login.login': 'demo', 'login.service': '', 'login.service.port': 8001, 'login.host': demo_server, 'login.db': demo_database, 'login.expanded': False, 'client.title': 'Tryton', 'client.modepda': False, 'client.toolbar': 'default', 'client.save_tree_width': True, 'client.save_tree_state': True, 'client.spellcheck': False, 'client.code_scanner_sound': True, 'client.lang': locale.getdefaultlocale()[0], 'client.language_direction': 'ltr', 'client.email': '', 'client.limit': 1000, 'client.check_version': True, 'client.bus_timeout': 10 * 60, 'icon.colors': '#3465a4,#555753,#cc0000', 'tree.colors': '#777,#198754,#ffc107,#dc3545', 'calendar.colors': '#fff,#3465a4', 'graph.color': '#3465a4', 'image.max_size': 10 ** 6, 'image.cache_size': 1024, 'doc.url': 'https://docs.tryton.org/%(version)s', 'bug.url': 'https://bugs.tryton.org/', 'download.url': 'https://downloads.tryton.org/', 'download.frequency': 60 * 60 * 8, 'menu.pane': 320, } self.config = {} self.options = {} self.arguments = [] def parse(self): parser = optparse.OptionParser(version=("Tryton %s" % __version__), usage="Usage: %prog [options] [url]") parser.add_option("-c", "--config", dest="config", help=_("specify alternate config file")) parser.add_option("-d", "--dev", action="store_true", default=False, dest="dev", help=_("development mode")) parser.add_option("-v", "--verbose", action="store_true", default=False, dest="verbose", help=_("logging everything at INFO level")) parser.add_option("-l", "--log-level", dest="log_level", help=_("specify the log level: " "DEBUG, INFO, WARNING, ERROR, CRITICAL")) parser.add_option("-o", "--log-ouput", dest="log_output", default=None, help=_("specify the file used to output logging information")) parser.add_option("-u", "--user", dest="login", help=_("specify the login user")) parser.add_option("-s", "--server", dest="host", help=_("specify the server hostname:port")) parser.add_option( '--no-thread', default=True, action='store_false', dest='thread', help=_("disable thread usage")) opt, self.arguments = parser.parse_args() self.rcfile = opt.config or os.path.join( get_config_dir(), 'tryton.conf') self.load() logging_config = {} if opt.log_output: logging_config['filename'] = opt.log_output loglevels = { 'DEBUG': logging.DEBUG, 'INFO': logging.INFO, 'WARNING': logging.WARNING, 'ERROR': logging.ERROR, 'CRITICAL': logging.CRITICAL, } if not opt.log_level: if opt.verbose: opt.log_level = 'INFO' else: opt.log_level = 'ERROR' logging_config['level'] = loglevels[opt.log_level.upper()] logging.basicConfig(**logging_config) self.options['dev'] = opt.dev for arg in ['login', 'host']: if getattr(opt, arg): self.options['login.' + arg] = getattr(opt, arg) self.options['thread'] = opt.thread def save(self): try: parser = configparser.ConfigParser() for entry in list(self.config.keys()): if not len(entry.split('.')) == 2: continue section, name = entry.split('.') if not parser.has_section(section): parser.add_section(section) parser.set(section, name, str(self.config[entry])) with open(self.rcfile, 'w') as fp: parser.write(fp) except IOError: logger.warn("Unable to write config file %s", self.rcfile) return False return True def load(self): parser = configparser.ConfigParser() try: parser.read([self.rcfile]) except configparser.Error: config_dir = os.path.dirname(self.rcfile) with tempfile.NamedTemporaryFile( delete=False, prefix='tryton_', suffix='.conf', dir=config_dir) as temp_file: temp_name = temp_file.name shutil.copy(self.rcfile, temp_name) logger.error( f"Failed to parse {self.rcfile}. " f"A backup can be found at {temp_name}", exc_info=True) return for section in parser.sections(): for (name, value) in parser.items(section): if value.lower() == 'true': value = True elif value.lower() == 'false': value = False if section == 'client' and name == 'limit': # First convert to float to be backward compatible with old # configuration value = int(float(value)) self.config[section + '.' + name] = value return True def __setitem__(self, key, value, config=True): self.options[key] = value if config: self.config[key] = value def __getitem__(self, key): return self.options.get(key, self.config.get(key, self.defaults.get(key))) CONFIG = ConfigManager() CURRENT_DIR = os.path.dirname(__file__) if not isinstance(CURRENT_DIR, str): CURRENT_DIR = str(CURRENT_DIR, sys.getfilesystemencoding()) PIXMAPS_DIR = os.path.join(CURRENT_DIR, 'data', 'pixmaps', 'tryton') if not os.path.isdir(PIXMAPS_DIR): try: import importlib.resources ref = importlib.resources.files('tryton') / 'data/pixmaps/tryton' with importlib.resources.as_file(ref) as path: PIXMAPS_DIR = path except ImportError: import pkg_resources PIXMAPS_DIR = pkg_resources.resource_filename( 'tryton', 'data/pixmaps/tryton') SOUNDS_DIR = os.path.join(CURRENT_DIR, 'data', 'sounds') if not os.path.isdir(SOUNDS_DIR): try: import importlib.resources ref = importlib.resources.files('tryton') / 'data/sounds' with importlib.resources.as_file(ref) as path: SOUNDS_DIR = path except ImportError: import pkg_resources SOUNDS_DIR = pkg_resources.resource_filename('tryton', 'data/sounds') TRYTON_ICON = GdkPixbuf.Pixbuf.new_from_file( os.path.join(PIXMAPS_DIR, 'tryton-icon.png')) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1745680285.1274436 tryton-7.0.24/tryton/data/0000755000175000017500000000000015003173635013526 5ustar00cedced././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1745680285.1607773 tryton-7.0.24/tryton/data/locale/0000755000175000017500000000000015003173635014765 5ustar00cedced././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1745680285.1241102 tryton-7.0.24/tryton/data/locale/bg/0000755000175000017500000000000015003173635015355 5ustar00cedced././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1745680285.1607773 tryton-7.0.24/tryton/data/locale/bg/LC_MESSAGES/0000755000175000017500000000000015003173635017142 5ustar00cedced././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1745680279.0 tryton-7.0.24/tryton/data/locale/bg/LC_MESSAGES/tryton.mo0000644000175000017500000002102015003173627021032 0ustar00cedced * 1 < @ ` j # %    &  $ . 6 L f y         ) / 5 M X h m s {                % 3 ? G \ n             ! ) 4 E L b r      I )-<AYx       # . 9 DO_ot z      (*, GARfCwFMOl;?Ts +1).1-`39$-ID 9% 4A8S (('2 Z!g 3"$7\1p"5!2 N*Y  $ & 2D[o/B/ 9!V x+&** DA<~8"" :3 n|    ) %2 #X |    '  !!*)!T!W!7Z!!?!4! " "%s/Record Name:All fieldsPredefined exportsActionActions...AddAdd an attachment to the recordAll filesAlways ignore this warning.Are you sure to remove this record?Are you sure to remove those records?Attachments...CSV ParametersC_onnectCancel connection to the Tryton serverClearClose TabCompareConcurrency ExceptionConnect the Tryton serverCopy selected textCreate a new recordCreate a new record Create new relationCut selected textDatabase:DeleteDelete selected record E-Mail...EditEdit User PreferencesEncoding:ErrorFalseFetching databases listField nameFile to Import:FindFuzzyHeight:Host / Database informationHost:ID:Image SizeImagesLaunch actionLimitLimit:Lines to Skip:LinkLoginMModel:NameNewNextNext RecordOpenOpen related recordsOpen relationOpen reportOpen...Open/Search relationPNG image (*.png)Paste copied textPreferencePreferencesPreviousPrevious RecordPrintPrint reportPrint...ProfileProfile EditorProfile:RelateRemove ReportReport BugReport...SaveSave AsSave As...Save this recordSearchSearch Limit SettingsSearch Limit...Search a record Select your actionSelectionShow plain textSpell CheckingSubject:SwitchSwitch viewThe following action requires to close all tabs. Do you want to continue?To:Translate viewTrueUnable to set locale %sUndelete selected record UnknownUnmark line for deletionUser name:Username:View _Logs...What is the name of this export?Width:WizardWrite AnywayYYour selection:_Actions..._Add_Cancel_Close Tab_Delete..._Duplicate_E-Mail..._Export Data..._Import Data..._New_Next_Previous_Print..._Relate..._Reload/Undo_Remove_Report..._Save_Switch Viewdhlogging everything at INFO levelmspecify alternate config filespecify the login userwyProject-Id-Version: PROJECT VERSION Report-Msgid-Bugs-To: EMAIL@ADDRESS POT-Creation-Date: 2025-04-26 17:11+0200 PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE Last-Translator: FULL NAME Language: bg Language-Team: bg Plural-Forms: nplurals=2; plural=(n != 1); MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: 8bit Generated-By: Babel 2.17.0 %s/Име на запис:Всички полетаПредварително зададени извличанияДействиеДействия...ДобавянеДобавяне на прикачен файл към записаВсички файловеВинаги игнорирай това предупреждение.Наистина ли искате да изтриете този запис?Наистина ли искате да изтриете тези записи?Прикачен файл...CSV параметриСвързванеОтказ от свръзване с Tryton сървъраИзчистванеЗатваряне на табСравняванеГрешка при достъпСвързване с Tryton сървъраКопиране на избрания текстСъздаване на нов записСъздаване на нов запис Създаване на нова връзкаИзрязване на избрания текстБаза данни:ИзтриванеИзтриване на избрания запис Email...РедактиранеРедактиране на потребителски настройкиКодиране:ГрешкаЛъжаВземане на списъка с бази данниИме на полеФайл за вмъкване:ТърсенеНеясноВисочина:Информация за Хост / База данниХост:ID:Размер на изображениеИзображенияЗапочване на действиеОграничениеОграничение:Редове за пропускане:ВръзкаПотребителско имеММодел:ИмеНовСледващСледващ записОтварянеОтваряне на свързани записиОтваряне на връзкаОтваряне на справкаОтваряне...Отваряне/Търсене на връзкаPNG изображение (*.png)Поставяне на копирания текстНастройкиНастройкиПредишенПредишен записПечатОтпечатване на справкаПечат...ПрофилРедактор на профилиПрофил:СвързаниИзтриване СправкиСъобщаване за грешкаСправки...СъхраняванеЗапис катоЗапис като ...Съхраняване на този записТърсенеНастройки на органичения за търсенеОграничение при търсене...Тъсене на записИзберете действиеИзборПоказване само на текстПроверка на правописОтносно:ПревключванеПревключване на изгледСледващите действия изискват затваряне на всички прозорци. Искате ли да продължите?До:Превод на изгледИстинаНе може да зададе локални настройки %sВъзтановяване на избрания запис НеизвестенИзчистване на ред за изтриванеПотребителско име:Потребителско име:Преглед на логовеКакво е името на това извличане?Ширина:ПомощникЗаписГВашия изборДействия...ДобавянеОтказЗатваряне на табИзтриване...ДублиранеEmail...Извличане на данни...Вмъкване на данни...НовСледващПредишенПечат...Свързани...Презареждане/ОбратноИзтриванеСправки...СъхраняванеПревключване на изгледдчзаписване на всичко на ниво INFOмукажете друг конфигурационен файлукажете потребителското имесг././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685599.0 tryton-7.0.24/tryton/data/locale/bg/LC_MESSAGES/tryton.po0000644000175000017500000005242614517761237021064 0ustar00cedced# Bulgarian (Bulgaria) translations for tryton. # Copyright (C) 2010 B2CK # This file is distributed under the same license as the tryton project. # FIRST AUTHOR , 2010. msgid "" msgstr "Content-Type: text/plain; charset=utf-8\n" msgid "specify alternate config file" msgstr "укажете друг конфигурационен файл" msgid "development mode" msgstr "" msgid "logging everything at INFO level" msgstr "записване на всичко на ниво INFO" msgid "specify the log level: DEBUG, INFO, WARNING, ERROR, CRITICAL" msgstr "" msgid "specify the file used to output logging information" msgstr "" msgid "specify the login user" msgstr "укажете потребителското име" #, fuzzy msgid "specify the server hostname:port" msgstr "укажете адреса на сървъра" msgid "disable thread usage" msgstr "" #, python-format msgid "Unable to set locale %s" msgstr "Не може да зададе локални настройки %s" #, fuzzy msgid ", " msgstr ", " msgid ",..." msgstr "" #, python-format msgid "%s (%s)" msgstr "" msgid "Select your action" msgstr "Изберете действие" #, fuzzy msgid "No action defined." msgstr "Не е зададено действие!" msgid "By: " msgstr "" msgid "Selection" msgstr "Избор" #, fuzzy msgid "Cancel" msgstr "Отказ" msgid "OK" msgstr "" msgid "Your selection:" msgstr "Вашия избор" msgid "Save As..." msgstr "Запис като ..." msgid "Do you want to proceed?" msgstr "" msgid "Always ignore this warning." msgstr "Винаги игнорирай това предупреждение." msgid "No" msgstr "" msgid "Yes" msgstr "" msgid "Concurrency Exception" msgstr "Грешка при достъп" msgid "This record has been modified while you were editing it." msgstr "" msgid "Cancel saving" msgstr "" msgid "Compare" msgstr "Сравняване" msgid "See the modified version" msgstr "" msgid "Write Anyway" msgstr "Запис" msgid "Save your current version" msgstr "" #, fuzzy, python-format msgid "Compare: %s" msgstr "Сравняване: %s" msgid "Close" msgstr "" #, fuzzy msgid "Application Error" msgstr "Грешка в приложението!" msgid "Report Bug" msgstr "Съобщаване за грешка" #, python-format msgid "Check URL: %s" msgstr "" msgid "Unable to check for new version." msgstr "" msgid "A new version is available!" msgstr "" msgid "Download" msgstr "" #, fuzzy msgid "Could not get a session." msgstr "Не може да се свърже със сървъра!" msgid "Too many requests. Try again later." msgstr "" msgid "Not found." msgstr "" msgid "Not Found." msgstr "" #, fuzzy msgid "..." msgstr "..." msgid "Search..." msgstr "" msgid "Create..." msgstr "" #, python-format msgid "Create \"%s\"..." msgstr "" #, fuzzy msgid "Value" msgstr "Лъжа" msgid "Displayed value" msgstr "" #, fuzzy msgid "Format" msgstr "Нормален" msgid "Display format" msgstr "" #, fuzzy msgid "Open the calendar" msgstr "Отваряне на календара" msgid "Date Format" msgstr "" msgid "Displayed date format" msgstr "" msgid "y" msgstr "г" msgid "True" msgstr "Истина" msgid "t" msgstr "" msgid "False" msgstr "Лъжа" msgid "Digits" msgstr "" msgid "The number of decimal" msgstr "" #, fuzzy msgid "Template" msgstr "Свързани" #, fuzzy msgid "Edit..." msgstr "Изход..." #, fuzzy msgid "View Logs..." msgstr "Преглед на логове" msgid "Attachments..." msgstr "Прикачен файл..." msgid "Notes..." msgstr "" msgid "Actions..." msgstr "Действия..." #, fuzzy msgid "Relate..." msgstr "Свързани..." msgid "Report..." msgstr "Справки..." msgid "Print..." msgstr "Печат..." msgid "E-Mail..." msgstr "Email..." msgid "Bold" msgstr "" msgid "Italic" msgstr "" msgid "Underline" msgstr "" msgid "Align Left" msgstr "" msgid "Align Center" msgstr "" msgid "Align Right" msgstr "" msgid "Justify" msgstr "" msgid "Foreground Color" msgstr "" msgid "Select a color" msgstr "" msgid "Y" msgstr "Г" msgid "M" msgstr "М" msgid "w" msgstr "с" msgid "d" msgstr "д" msgid "h" msgstr "ч" msgid "m" msgstr "м" msgid "s" msgstr "" #, fuzzy msgid "Preferences..." msgstr "Предпочитания..." #, fuzzy msgid "Toolbar" msgstr "Лента с инструменти" #, fuzzy msgid "Default" msgstr "По подразбиране" #, fuzzy msgid "Text and Icons" msgstr "Текс и икони" #, fuzzy msgid "Text" msgstr "Текст" #, fuzzy msgid "Icons" msgstr "Икони" #, fuzzy msgid "Form" msgstr "Форма" msgid "Save Column Width" msgstr "" #, fuzzy msgid "Save Tree State" msgstr "Запазване на състояние разгърнато дърво" msgid "Spell Checking" msgstr "Проверка на правопис" msgid "Play Sound for Code Scanner" msgstr "" msgid "PDA Mode" msgstr "" msgid "Search Limit..." msgstr "Ограничение при търсене..." msgid "Check Version" msgstr "" #, fuzzy msgid "Options" msgstr "Настройки" #, fuzzy msgid "Documentation..." msgstr "Действия..." #, fuzzy msgid "Keyboard Shortcuts..." msgstr "Бързи клавиши..." #, fuzzy msgid "About..." msgstr "Относно..." #, fuzzy msgid "Help" msgstr "Помощ" msgid "No result found." msgstr "" msgid "Favorites" msgstr "" msgid "Manage..." msgstr "" msgid "Action" msgstr "Действие" msgid "" "The following action requires to close all tabs.\n" "Do you want to continue?" msgstr "" "Следващите действия изискват затваряне на всички прозорци.\n" "Искате ли да продължите?" msgid "Application Shortcuts" msgstr "" msgid "Global" msgstr "" msgid "Preferences" msgstr "Настройки" msgid "Search menu" msgstr "" msgid "Toggle menu" msgstr "" #, fuzzy msgid "Previous tab" msgstr "Предишен" msgid "Next tab" msgstr "" msgid "Shortcuts" msgstr "" msgid "Quit" msgstr "" msgid "Edition Shortcuts" msgstr "" msgid "Text Entries" msgstr "" msgid "Cut selected text" msgstr "Изрязване на избрания текст" msgid "Copy selected text" msgstr "Копиране на избрания текст" msgid "Paste copied text" msgstr "Поставяне на копирания текст" msgid "Next entry" msgstr "" msgid "Previous entry" msgstr "" msgid "Relation Entries" msgstr "" msgid "Create new relation" msgstr "Създаване на нова връзка" msgid "Open/Search relation" msgstr "Отваряне/Търсене на връзка" msgid "List Entries" msgstr "" msgid "Switch view" msgstr "Превключване на изглед" msgid "Create/Select new line" msgstr "" msgid "Open relation" msgstr "Отваряне на връзка" msgid "Mark line for deletion/removal" msgstr "" msgid "Mark line for removal" msgstr "" msgid "Unmark line for deletion" msgstr "Изчистване на ред за изтриване" msgid "List/Tree Shortcuts" msgstr "" msgid "Move Cursor" msgstr "" msgid "Move right" msgstr "" msgid "Move left" msgstr "" msgid "Move up" msgstr "" msgid "Move down" msgstr "" msgid "Move up of one page" msgstr "" msgid "Move down of one page" msgstr "" msgid "Move to top" msgstr "" msgid "Move to bottom" msgstr "" msgid "Move to parent" msgstr "" #, fuzzy msgid "Edition" msgstr "Редактиране" #, fuzzy msgid "Copy selected rows" msgstr "Копиране на избрания текст" msgid "Insert copied rows" msgstr "" msgid "Select all" msgstr "" msgid "Unselect all" msgstr "" msgid "Select parent" msgstr "" msgid "Select/Activate current row" msgstr "" msgid "Toggle selection" msgstr "" msgid "Expand/Collapse" msgstr "" msgid "Expand row" msgstr "" msgid "Collapse row" msgstr "" msgid "Toggle row" msgstr "" msgid "Collapse all rows" msgstr "" msgid "Expand all rows" msgstr "" msgid "Close Tab" msgstr "Затваряне на таб" msgid "modularity, scalability and security" msgstr "" msgid "translator-credits" msgstr "" #, fuzzy, python-format msgid "Attachments (%s)" msgstr "Прикачен файл(%s)" msgid "Code Scanner" msgstr "" msgid "Code" msgstr "" msgid "Profile Editor" msgstr "Редактор на профили" msgid "Profile" msgstr "Профил" msgid "Add new profile" msgstr "" msgid "Remove selected profile" msgstr "" msgid "Host:" msgstr "Хост:" msgid "Database:" msgstr "База данни:" msgid "Fetching databases list" msgstr "Вземане на списъка с бази данни" msgid "Username:" msgstr "Потребителско име:" #, fuzzy msgid "Incompatible version of the server." msgstr "Несъвместима версия на сървъра!" #, fuzzy msgid "Could not connect to the server." msgstr "Не може да се свърже със сървъра!" msgid "Login" msgstr "Потребителско име" msgid "_Cancel" msgstr "Отказ" msgid "Cancel connection to the Tryton server" msgstr "Отказ от свръзване с Tryton сървъра" msgid "C_onnect" msgstr "Свързване" msgid "Connect the Tryton server" msgstr "Свързване с Tryton сървъра" msgid "Profile:" msgstr "Профил:" msgid "Host / Database information" msgstr "Информация за Хост / База данни" msgid "User name:" msgstr "Потребителско име:" #, fuzzy msgid "Unable to complete email entry" msgstr "Не може да зададе локални настройки %s" #, fuzzy, python-format msgid "E-mail %s" msgstr "Email %s" msgid "To:" msgstr "До:" #, fuzzy msgid "Cc:" msgstr "CC:" msgid "Bcc:" msgstr "" msgid "Subject:" msgstr "Относно:" #, fuzzy msgid "Body" msgstr "Тяло:" #, fuzzy msgid "Reports" msgstr "Справки" #, fuzzy msgid "Attachments" msgstr "Прикачен файл:" msgid "Files" msgstr "" msgid "Send" msgstr "" #, fuzzy msgid "Select File" msgstr "Избор" #, fuzzy msgid "Remove File" msgstr "Изтриване " msgid "Select" msgstr "" msgid "Add..." msgstr "" #, fuzzy msgid "Preview" msgstr "Предишен" msgid "Previous" msgstr "Предишен" msgid "Next" msgstr "Следващ" #, fuzzy, python-format msgid "Attachment (%s)" msgstr "Прикачен файл(%s)" #, python-format msgid "Note (%d/%d)" msgstr "" #, fuzzy msgid "You have to select one record." msgstr "Трябва да изберете един запис!" msgid "Are you sure to remove this record?" msgstr "Наистина ли искате да изтриете този запис?" msgid "Are you sure to remove those records?" msgstr "Наистина ли искате да изтриете тези записи?" #, fuzzy msgid "Records not removed." msgstr "Записа не е изтрит!" #, fuzzy msgid "Records removed." msgstr "Записите изтрити!" #, fuzzy msgid "Working now on the duplicated record(s)." msgstr "Работите върху дублиран(и) запис(и)!" #, fuzzy msgid "Record saved." msgstr "Записа съхранен!" #, fuzzy msgid "" "This record has been modified\n" "do you want to save it?" msgstr "" "Този запис е променен\n" "искате ли да го запишете ?" msgid "Launch action" msgstr "Започване на действие" msgid "Relate" msgstr "Свързани" msgid "Open related records" msgstr "Отваряне на свързани записи" msgid "Report" msgstr "Справки" msgid "Open report" msgstr "Отваряне на справка" msgid "Print" msgstr "Печат" msgid "Print report" msgstr "Отпечатване на справка" msgid "_Copy URL" msgstr "" msgid "Copy URL into clipboard" msgstr "" msgid "Unknown" msgstr "Неизвестен" msgid "Limit" msgstr "Ограничение" msgid "Search Limit Settings" msgstr "Настройки на органичения за търсене" msgid "Limit:" msgstr "Ограничение:" #, python-format msgid "Logs (%s)" msgstr "" msgid "Model:" msgstr "Модел:" msgid "ID:" msgstr "ID:" #, fuzzy msgid "Created by:" msgstr "Дата на създаване:" #, fuzzy msgid "Created at:" msgstr "Дата на създаване:" #, fuzzy msgid "Last Modified by:" msgstr "Последно променен от:" #, fuzzy msgid "Last Modified at:" msgstr "Дата на последна промяна:" #, python-format msgid "Notes (%s)" msgstr "" msgid "Edit User Preferences" msgstr "Редактиране на потребителски настройки" msgid "Preference" msgstr "Настройки" #, fuzzy msgid "Revision" msgstr "Предишен" #, fuzzy msgid "Select a revision" msgstr "Избор:" #, fuzzy msgid "Revision:" msgstr "Предишен" msgid "_Switch View" msgstr "Превключване на изглед" #, fuzzy msgid "Switch View" msgstr "Превключване на изглед" msgid "_Previous" msgstr "Предишен" msgid "Previous Record" msgstr "Предишен запис" msgid "_Next" msgstr "Следващ" msgid "Next Record" msgstr "Следващ запис" #, fuzzy msgid "_Search" msgstr "Търсене" msgid "_New" msgstr "Нов" msgid "Create a new record" msgstr "Създаване на нов запис" msgid "_Save" msgstr "Съхраняване" msgid "Save this record" msgstr "Съхраняване на този запис" msgid "_Reload/Undo" msgstr "Презареждане/Обратно" #, fuzzy msgid "Reload/Undo" msgstr "Презареждане/Обратно" msgid "_Duplicate" msgstr "Дублиране" msgid "_Delete..." msgstr "Изтриване..." msgid "View _Logs..." msgstr "Преглед на логове" msgid "Show revisions..." msgstr "" #, fuzzy msgid "A_ttachments..." msgstr "Прикачен файл:" msgid "Add an attachment to the record" msgstr "Добавяне на прикачен файл към записа" msgid "_Notes..." msgstr "" msgid "Add a note to the record" msgstr "" msgid "_Actions..." msgstr "Действия..." msgid "_Relate..." msgstr "Свързани..." msgid "_Report..." msgstr "Справки..." msgid "_Print..." msgstr "Печат..." msgid "_E-Mail..." msgstr "Email..." #, fuzzy msgid "Send an e-mail using the record" msgstr "Съхраняване на този запис" msgid "_Export Data..." msgstr "Извличане на данни..." msgid "_Import Data..." msgstr "Вмъкване на данни..." msgid "Copy _URL..." msgstr "" msgid "_Close Tab" msgstr "Затваряне на таб" msgid "All fields" msgstr "Всички полета" msgid "_Add" msgstr "Добавяне" msgid "_Remove" msgstr "Изтриване" #, fuzzy msgid "_Clear" msgstr "Изчистване" msgid "Fields selected" msgstr "" msgid "CSV Parameters" msgstr "CSV параметри" msgid "Delimiter:" msgstr "" msgid "Quote char:" msgstr "" msgid "Encoding:" msgstr "Кодиране:" msgid "Use locale format" msgstr "" msgid "Field name" msgstr "Име на поле" #, python-format msgid "CSV Export: %s" msgstr "" #, fuzzy msgid "_Save Export" msgstr "Запис на извличане" #, fuzzy msgid "_URL Export" msgstr "Запис на извличане" #, fuzzy msgid "_Delete Export" msgstr "Изтриване на извличане" msgid "Predefined exports" msgstr "Предварително зададени извличания" msgid "Name" msgstr "Име" msgid "Open" msgstr "Отваряне" msgid "Save" msgstr "Съхраняване" #, fuzzy msgid "Listed Records" msgstr "Редактиране на избрания запис" #, fuzzy msgid "Selected Records" msgstr "Редактиране на избрания запис" msgid "Ignore search limit" msgstr "" #, fuzzy msgid "Add field names" msgstr "Добавяне на имена на полета" #, python-format msgid "%s (string)" msgstr "" #, python-format msgid "%s (model name)" msgstr "" #, python-format msgid "%s/Record Name" msgstr "%s/Име на запис" msgid "What is the name of this export?" msgstr "Какво е името на това извличане?" #, python-format msgid "Override '%s' definition?" msgstr "" #, fuzzy, python-format msgid "%d record saved." msgstr "%d заспис съхранен!" #, fuzzy, python-format msgid "%d records saved." msgstr "%d записа съхранени!" msgid "Export failed" msgstr "" msgid "Link" msgstr "Връзка" msgid "Delete" msgstr "Изтриване" msgid "Discard changes" msgstr "" msgid "Save and New" msgstr "" msgid "Add and New" msgstr "" msgid "Add" msgstr "Добавяне" msgid "Apply changes" msgstr "" msgid "Switch" msgstr "Превключване" msgid "Remove " msgstr "Изтриване " msgid "Create a new record " msgstr "Създаване на нов запис " msgid "Delete selected record " msgstr "Изтриване на избрания запис " msgid "Undelete selected record " msgstr "Възтановяване на избрания запис " #, python-format msgid "CSV Import: %s" msgstr "" #, fuzzy msgid "_Auto-Detect" msgstr "Автоматично разпознаване" msgid "File to Import:" msgstr "Файл за вмъкване:" msgid "Open..." msgstr "Отваряне..." msgid "Lines to Skip:" msgstr "Редове за пропускане:" #, fuzzy msgid "You must select an import file first." msgstr "Първо трябва да изберете файл за вмъкване!" msgid "Detection failed" msgstr "" #, python-format msgid "Unknown column header \"%s\"" msgstr "" msgid "Error" msgstr "Грешка" msgid "Import failed" msgstr "" #, fuzzy, python-format msgid "%d record imported." msgstr "%d запис вмъкнат!" #, fuzzy, python-format msgid "%d records imported." msgstr "%d записи вмъкнати!" msgid "Search" msgstr "Търсене" msgid "New" msgstr "Нов" #, python-format msgid "Search %s" msgstr "" msgid "Wizard" msgstr "Помощник" #, fuzzy msgid "ID" msgstr "д" #, fuzzy msgid "Created by" msgstr "Дата на създаване:" #, fuzzy msgid "Created at" msgstr "Дата на създаване:" #, fuzzy msgid "Edited by" msgstr "Редактиране" #, fuzzy msgid "Edited at" msgstr "Редактиране" #, fuzzy msgid "Unable to set view tree state" msgstr "Не може да зададе локални настройки %s" #, python-format msgid "\"%s\" is not valid according to its domain." msgstr "" #, python-format msgid "\"%s\" is required." msgstr "" #, python-format msgid "The values of \"%s\" are not valid." msgstr "" msgid "Pre-validation" msgstr "" msgid ":" msgstr ":" msgid "Scan" msgstr "" msgid "Image Size" msgstr "Размер на изображение" msgid "Width:" msgstr "Ширина:" msgid "Height:" msgstr "Височина:" msgid "PNG image (*.png)" msgstr "PNG изображение (*.png)" msgid "Save As" msgstr "Запис като" #, fuzzy msgid "Image size too large." msgstr "Размера на изображението е много голям!" msgid "Copy" msgstr "" msgid "Paste" msgstr "" msgid ".." msgstr "" msgid "Open filters" msgstr "" msgid "Show bookmarks of filters" msgstr "" msgid "Remove this bookmark" msgstr "" msgid "Bookmark this filter" msgstr "" msgid "Show active records" msgstr "" msgid "Show inactive records" msgstr "" msgid "Bookmark Name:" msgstr "" msgid "Find" msgstr "Търсене" #, fuzzy msgid "Today" msgstr "Тяло:" msgid "go back" msgstr "" msgid "go forward" msgstr "" msgid "previous year" msgstr "" msgid "next year" msgstr "" msgid "Day" msgstr "" msgid "Week" msgstr "" #, fuzzy msgid "Month" msgstr "Превключване на изглед" msgid "Select..." msgstr "" msgid "Clear" msgstr "Изчистване" msgid "All files" msgstr "Всички файлове" msgid "Show plain text" msgstr "Показване само на текст" msgid "Add value" msgstr "" #, python-format msgid "Remove \"%s\"" msgstr "" msgid "Images" msgstr "Изображения" #, fuzzy msgid "Add existing record" msgstr "Съхраняване на този запис" #, fuzzy msgid "Remove selected record" msgstr "Изтриване на избрания запис " #, fuzzy msgid "Open the record " msgstr "Отваряне на запис" msgid "Clear the field " msgstr "" msgid "Search a record " msgstr "Тъсене на запис" #, fuzzy msgid "Edit selected record" msgstr "Редактиране на избрания запис" #, fuzzy msgid "Delete selected record" msgstr "Изтриване на избрания запис " #, python-format msgid "%s%%" msgstr "" msgid "Choose a language" msgstr "" #, fuzzy msgid "Translation" msgstr "Добавяне на превод" msgid "Edit" msgstr "Редактиране" msgid "Fuzzy" msgstr "Неясно" #, fuzzy msgid "You need to save the record before adding translations." msgstr "Трябва да съхраните записа преди да добавите преводи!" #, fuzzy msgid "No other language available." msgstr "Няма наличен друг език!" msgid "#ERROR" msgstr "" msgid "Translate view" msgstr "Превод на изглед" ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1745680285.1241102 tryton-7.0.24/tryton/data/locale/ca/0000755000175000017500000000000015003173635015350 5ustar00cedced././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1745680285.1607773 tryton-7.0.24/tryton/data/locale/ca/LC_MESSAGES/0000755000175000017500000000000015003173635017135 5ustar00cedced././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1745680279.0 tryton-7.0.24/tryton/data/locale/ca/LC_MESSAGES/tryton.mo0000644000175000017500000004607115003173627021042 0ustar00cedcedL |*}  %16EHMPTVh  ?K_o      #%4Z jv&  2@ D R`rx     0=P c    &2 D NZ^fm  !2 ; EOTj        & 1 A G L ] b i o v ~         #  !3!:!B!X!j! |!!!!! !!!! !! !!","3" 9" E"O" e" o"z"" """""" " """"#!# 2# =# H# U#`#i#l# q#~# # #######$$.$4$F$b$ q$ |$$$$$$ $$ $$$$%% % !%/%D%U% \%f% w% % % %%%%% % %&& &#&(& 0&;&M& ]&j&{&&& &&&& && ' ''-' ?' J'X' k'u'' ''' ''' ((/(A(P(Y( `( l(x(( ((I((!)5$)8Z))) ) ))#))) * * *2*Q*i** **** ** + + + &+ 3+A+ F+g+n+(u+ ++++%+7+., >,J, O,\,d, k, v,, , , ,,,,, , , , , -- -$- *-7- ?- L-X-Z-k-- -- --$- - ---3.<I.. ......(g000000011 11;1@1T1W1\1_1c1e1{1#111"1 1 22 2$2,2I2 g2u2222 22233!$3F3Y3 r3/313 33 3 4444%4@4[4a4u44 4 4+4444 55,535N5T5c5h5z5 55 555556!6?6(Y6"6 666667 7 )7 37>7X7 g7t7 x777&7 77778#8:8J8[8l8r888888 8 889 9,929L9[9m99 9#9 9999 9 99: :::(": K:U:X:g:n::$:::8:;1; 9;D;^;w;;;;;;;;;<<< !<#-<Q<l<s< w< <<<< < << << ====.=?=Q=T=%p==== = ==== >>)> ;>H>Z>p>x>>*>>>>>"? &? 4? B?P? a?o?x??? ?? ??????@ #@D@ Y@ d@r@@ @@@@@!A&A/A BAMAVA _AiAnA }AAA AAAABB!B3BGB^BmB BBBBBBB C"C@C WCaC2gCCCCCC DD 5D?DOD_D oDyD~D DLDD#E3%E6YEEEE EE,E EF F F5)F8_F"F/F&F G G'(G$PGuGG G GGGGG&GH H-&HTHiHkHnH/H4HH III 5IAIII \IiI ~IIIIII I I I IIJ!J *J7J=JOJVJgJxJzJJ J JJ,JJ&JK (K5K,7KEdK>KKKLL5L7L"%s" is not valid according to its domain."%s" is required.#ERROR%d record imported.%d record saved.%d records imported.%d records saved.%s (%s)%s (model name)%s (string)%s%%%s/Record Name, ,........:All fieldsFields selectedPredefined exportsCreate...Search...A new version is available!A_ttachments...About...ActionActions...AddAdd a note to the recordAdd an attachment to the recordAdd and NewAdd existing recordAdd field namesAdd new profileAdd valueAdd...Align CenterAlign LeftAlign RightAll filesAlways ignore this warning.Application ErrorApplication ShortcutsApply changesAre you sure to remove this record?Are you sure to remove those records?Attachment (%s)AttachmentsAttachments (%s)Attachments...Bcc:BodyBoldBookmark Name:Bookmark this filterBy: CSV Export: %sCSV Import: %sCSV ParametersC_onnectCancelCancel connection to the Tryton serverCancel savingCc:Check URL: %sCheck VersionChoose a languageClearClear the field CloseClose TabCodeCode ScannerCollapse all rowsCollapse rowCompareCompare: %sConcurrency ExceptionConnect the Tryton serverCopyCopy URL into clipboardCopy _URL...Copy selected rowsCopy selected textCould not connect to the server.Could not get a session.Create "%s"...Create a new recordCreate a new record Create new relationCreate/Select new lineCreated atCreated at:Created byCreated by:Cut selected textDatabase:Date FormatDayDefaultDeleteDelete selected recordDelete selected record Delimiter:Detection failedDigitsDiscard changesDisplay formatDisplayed date formatDisplayed valueDo you want to proceed?Documentation...DownloadE-Mail...E-mail %sEditEdit User PreferencesEdit selected recordEdit...Edited atEdited byEditionEdition ShortcutsEncoding:ErrorExpand all rowsExpand rowExpand/CollapseExport failedFalseFavoritesFetching databases listField nameFile to Import:FilesFindForeground ColorFormFormatFuzzyGlobalHeight:HelpHost / Database informationHost:IDID:IconsIgnore search limitImage SizeImage size too large.ImagesImport failedIncompatible version of the server.Insert copied rowsItalicJustifyKeyboard Shortcuts...Last Modified at:Last Modified by:Launch actionLimitLimit:Lines to Skip:LinkList EntriesList/Tree ShortcutsListed RecordsLoginLogs (%s)MManage...Mark line for deletion/removalMark line for removalModel:MonthMove CursorMove downMove down of one pageMove leftMove rightMove to bottomMove to parentMove to topMove upMove up of one pageNameNewNextNext RecordNext entryNext tabNoNo action defined.No other language available.No result found.Not Found.Not found.Note (%d/%d)Notes (%s)Notes...OKOpenOpen filtersOpen related recordsOpen relationOpen reportOpen the calendarOpen the record Open...Open/Search relationOptionsOverride '%s' definition?PDA ModePNG image (*.png)PastePaste copied textPlay Sound for Code ScannerPre-validationPreferencePreferencesPreferences...PreviewPreviousPrevious RecordPrevious entryPrevious tabPrintPrint reportPrint...ProfileProfile EditorProfile:QuitQuote char:Record saved.Records not removed.Records removed.RelateRelate...Relation EntriesReload/UndoRemove "%s"Remove Remove FileRemove selected profileRemove selected recordRemove this bookmarkReportReport BugReport...ReportsRevisionRevision:SaveSave AsSave As...Save Column WidthSave Tree StateSave and NewSave this recordSave your current versionScanSearchSearch %sSearch Limit SettingsSearch Limit...Search a record Search menuSee the modified versionSelectSelect FileSelect a colorSelect a revisionSelect allSelect parentSelect your actionSelect...Select/Activate current rowSelected RecordsSelectionSendSend an e-mail using the recordShortcutsShow active recordsShow bookmarks of filtersShow inactive recordsShow plain textShow revisions...Spell CheckingSubject:SwitchSwitch ViewSwitch viewTemplateTextText EntriesText and IconsThe following action requires to close all tabs. Do you want to continue?The number of decimalThe values of "%s" are not valid.This record has been modified do you want to save it?This record has been modified while you were editing it.To:TodayToggle menuToggle rowToggle selectionToo many requests. Try again later.ToolbarTranslate viewTranslationTrueUnable to check for new version.Unable to complete email entryUnable to set locale %sUnable to set view tree stateUndelete selected record UnderlineUnknownUnknown column header "%s"Unmark line for deletionUnselect allUse locale formatUser name:Username:ValueView Logs...View _Logs...WeekWhat is the name of this export?Width:WizardWorking now on the duplicated record(s).Write AnywayYYesYou have to select one record.You must select an import file first.You need to save the record before adding translations.Your selection:_Actions..._Add_Auto-Detect_Cancel_Clear_Close Tab_Copy URL_Delete Export_Delete..._Duplicate_E-Mail..._Export Data..._Import Data..._New_Next_Notes..._Previous_Print..._Relate..._Reload/Undo_Remove_Report..._Save_Save Export_Search_Switch View_URL Exportddevelopment modedisable thread usagego backgo forwardhlogging everything at INFO levelmmodularity, scalability and securitynext yearprevious yearsspecify alternate config filespecify the file used to output logging informationspecify the log level: DEBUG, INFO, WARNING, ERROR, CRITICALspecify the login userspecify the server hostname:portttranslator-creditswyProject-Id-Version: PROJECT VERSION Report-Msgid-Bugs-To: EMAIL@ADDRESS POT-Creation-Date: 2025-04-26 17:11+0200 PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE Last-Translator: FULL NAME Language: ca Language-Team: ca Plural-Forms: nplurals=2; plural=(n != 1); MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: 8bit Generated-By: Babel 2.17.0 "%s" no és vàlid segons el seu domini."%s" és obligatori.#ERRORS'ha importat %d registre.S'ha desat %d registre.S'han importat %d registres.S'han desat %d registres.%s (%s)%s (nom del model)%s (text)%s%%%s/Nom del registre, ,........:Tots els campsCamps seleccionatsExportacions predeterminadesCrea...Cerca...Hi ha una nova versió disponible!Adjun_ts...Quant a...AccióAccions...AfegeixAfegeix una nota al registreAfegeix un adjunt al registreAfegeix i nouAfegeix un registre existentAfegeix noms de campAfegeix un nou perfilAfegeix un valorAfegeix...Alinear al centreAlinear a l'esquerraAlinear a la dretaTots els fitxersIgnora sempre aquest advertiment.Error d'aplicacióDreceres de l'aplicacióAplica canvisEsteu segur que voleu eliminar aquest registre?Esteu segur que voleu eliminar aquests registres?Adjunts (%s)AdjuntsAdjunts (%s)Adjunts...Cco:MissatgeNegretaNom de la cerca preferida:Desa com a cerca preferidaPer: Exportació CSV: %sImportació CSV: %sParàmetres CSVC_onnectaCancel·laCancel·la connexió amb el servidor TrytonNo desis els canvisCc:URL de comprovació: %sComprova versióEscull un idiomaNetejaEliminar el registre TancaTanca pestanyaCodiEscàner de codisContrau totes les filesContrau filaComparaCompara: %sExcepció de concurrènciaConnecta amb el servidor TrytonCopiaCopia l'URL al porta-retallsCopia l'_URL...Copia les files seleccionadesCopia el text seleccionatNo s'ha pogut connectar amb el servidor.No s'ha pogut obtenir una sessió.Crea "%s"...Crea un nou registreCrea un nou registre Crea una nova relacióCrear/Seleccionar línia novaCreat elCreat el:Creat perCreat per:Talla el text seleccionatBase de dades:Format datesDiaPer defecteEliminaElimina el registre seleccionatElimina el registre seleccionat Separador:Ha fallat la deteccióDígitsDescartar els canvisFormat a mostrarFormat dates a mostrarValor a mostrarVoleu continuar?Documentació...BaixaCorreu electrònic...Correu electrònic %sEditaEdita preferències d'usuariEdita registre seleccionatEdita...Editat elEditat perEditaAccessos directes a l'edicióCodificació:ErrorExpandeix totes les filesExpandeix filaExpandeix/ContrauHa fallat l'exportacióFalsPreferitsRecuperant llista de bases de dadesNom del campFitxer a importar:FitxersCercaColor de fonsFormulariFormatDubtosaGlobalAltura:AjudaInformació del Servidor / Base de dadesServidor:IDIdentificador:IconesIgnora límit de cercaMida de la imatgeLa mida de la imatge és massa gran.ImatgesHa fallat la importacióEl client no és compatible amb la versió del servidor.Inserta les files copiadesCursivaJustificarCombinacions de tecles...Última modificació el:Última modificació per:Executa accióLímitLímit:Línies a ometre:EnllaçLlistat d'entradesDrecera de llista/arbreRegistres llistatsAccedeixRegistres (%s)MGestiona...Marcar línia per eliminar/suprimirMarcar línia per eliminarModel:MesMou cursorMou avallMou avall una pàginaMou a l'esquerraMou a la dretaMou al finalMou al pareMou al principiMou amuntMou amunt una pàginaNomNouSegüentRegistre següentSegüent entradaPestanya següentNoNo s'ha definit cap acció.No hi ha cap altre idioma disponible.No s'han trobat resultats.No s'han trobat.No s'han trobat.Nota (%d/%d)Notes (%s)Notes...D'acordObreObre filtresObre registres relacionatsObre una relacióObre informeObre el calendariObre el registre Obre...Obre/Cerca una relacióOpcionsVoleu sobreescriure la definició de '%s'?Mode PDAImatge PNG (*.png)EnganxaEnganxa el text seleccionatActiva el so del escàner de codisPrevalidacióPreferènciesPreferènciesPreferències...PrevisualitzaAnteriorRegistre anteriorEntrada anteriorPestanya anteriorImprimeixImprimeix informeImprimeix...PerfilEditor de perfilsPerfil:SortirDelimitador de text:Registre desat.Els registres no s'han eliminat.Registres eliminats.RelacionatRelacionat...Entrades de relacionsRecarrega/DesfésEliminar "%s"Elimina Elimina fitxerElimina el perfil seleccionatElimina el registre seleccionatElimina de les cerques preferidesInformesInforma de l'errorInforme...InformesRevisióRevisió:DesaAnomena i desaDesa com...Guarda l'ample de la columnaDesa l'estat de l'arbreGuarda i nouDesa aquest registreDeseu els vostres canvisEscanejaCercaCerca %sPreferències del límit de cerca_Límit de cerca...Cerca un registre Menú de cercaMostra la versió modificadaSeleccionaSelecciona un fitxerSeleccioneu un colorSeleccioneu una revisióSelecciona totSelecciona pareSeleccioneu la vostra accióSelecciona...Selecciona/Activa fila actualRegistres seleccionatsSeleccióEnviaEnvia un correu electrònic utilitzant el registreDreceresMostra registres actiusMostra les cerques preferidesMostra registres inactiusMostra com a text plaMostra revisions...Correcció ortogràficaAssumpte:Canvia la vistaCanvia la vistaCanvia la vistaPlantillaTextEntrades de textText i iconesL'acció seleccionada requereix tancar totes les pestanyes. Voleu continuar?El número de decimalsEls valors de "%s" no són vàlids.Aquest registre ha estat modificat. Voleu desar-lo?Aquest registre ha estat modificat mentre l'editàveu.A:AvuiCommuta el menúCommuta filaCommuta seleccióMassa peticions. Proveu-ho de nou més tard.Barra d'einesTradueix la vistaTraduccióVerdaderNo s'ha pogut comprovar si existeix una nova versió.No s'ha pogut completar l'entrada del correu electrònicNo s'ha pogut establir l'idioma %sNo s'ha pogut desar l'estat de la vista d'arbreRecupera el registre seleccionat SubratllatDesconegutCapçalera de columan desconeguda: "%s"Desmarca la línia per ser eliminadaDeselecciona totUtilitza format localNom d'usuari:Nom d'usuari:ValorVeure registres...Veure _registre...SetmanaQuin és el nom d'aquesta exportació?Amplada:AssistentEstà treballant amb un registre(s) duplicat.Desa de totes formesASiHa de seleccionar un registre.Primer heu de seleccionar un fitxer a importar.Heu de desar el registre abans d'afegir traduccions.La vostra selecció:_Accions..._AfegeixDetecta _automàticament_Cancel·la_Neteja_Tanca la pestanya_Copia l'URL_Elimina exportació_Elimina..._Duplica_Correu electrònic..._Exporta dades..._Importa dades..._NouSeg_üent_Notes..._AnteriorIm_primeix..._Relacionat..._Recarrega/Desfés_Elimina_Informes..._Desa_Desa exportació_CercaCanvia la vi_sta_URL exportaciódmode desenvolupamentdesactiva l'ús de filsvés enrerevés endavanthRegistra tota la informació amb nivell INFOmmodularitat, escalabilitat i seguretatl'any següentany anteriorsIndica un fitxer de configuració alternatiuIndica el fitxer a utiltizar per la sortida d'informació de registreindica el nivell de log: DEBUG, INFO, WARNING, ERROR, CRITICALIndica l'usuariIndica el nom:port del servidorvSergi Almacellas AbellanaSa././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685599.0 tryton-7.0.24/tryton/data/locale/ca/LC_MESSAGES/tryton.po0000644000175000017500000005031614517761237021053 0ustar00cedced# Catalan translations for tryton. # Copyright (C) 2012 Zikzakmedia SL # This file is distributed under the same license as the tryton project. # Sergi Almacellas Abellana 2012. # Jordi Esteve Cusiné 2012. msgid "" msgstr "Content-Type: text/plain; charset=utf-8\n" msgid "specify alternate config file" msgstr "Indica un fitxer de configuració alternatiu" msgid "development mode" msgstr "mode desenvolupament" msgid "logging everything at INFO level" msgstr "Registra tota la informació amb nivell INFO" msgid "specify the log level: DEBUG, INFO, WARNING, ERROR, CRITICAL" msgstr "indica el nivell de log: DEBUG, INFO, WARNING, ERROR, CRITICAL" msgid "specify the file used to output logging information" msgstr "Indica el fitxer a utiltizar per la sortida d'informació de registre" msgid "specify the login user" msgstr "Indica l'usuari" msgid "specify the server hostname:port" msgstr "Indica el nom:port del servidor" msgid "disable thread usage" msgstr "desactiva l'ús de fils" #, python-format msgid "Unable to set locale %s" msgstr "No s'ha pogut establir l'idioma %s" msgid ", " msgstr ", " msgid ",..." msgstr ",..." #, python-format msgid "%s (%s)" msgstr "%s (%s)" msgid "Select your action" msgstr "Seleccioneu la vostra acció" msgid "No action defined." msgstr "No s'ha definit cap acció." msgid "By: " msgstr "Per: " msgid "Selection" msgstr "Selecció" msgid "Cancel" msgstr "Cancel·la" msgid "OK" msgstr "D'acord" msgid "Your selection:" msgstr "La vostra selecció:" msgid "Save As..." msgstr "Desa com..." msgid "Do you want to proceed?" msgstr "Voleu continuar?" msgid "Always ignore this warning." msgstr "Ignora sempre aquest advertiment." msgid "No" msgstr "No" msgid "Yes" msgstr "Si" msgid "Concurrency Exception" msgstr "Excepció de concurrència" msgid "This record has been modified while you were editing it." msgstr "Aquest registre ha estat modificat mentre l'editàveu." msgid "Cancel saving" msgstr "No desis els canvis" msgid "Compare" msgstr "Compara" msgid "See the modified version" msgstr "Mostra la versió modificada" msgid "Write Anyway" msgstr "Desa de totes formes" msgid "Save your current version" msgstr "Deseu els vostres canvis" #, python-format msgid "Compare: %s" msgstr "Compara: %s" msgid "Close" msgstr "Tanca" msgid "Application Error" msgstr "Error d'aplicació" msgid "Report Bug" msgstr "Informa de l'error" #, python-format msgid "Check URL: %s" msgstr "URL de comprovació: %s" msgid "Unable to check for new version." msgstr "No s'ha pogut comprovar si existeix una nova versió." msgid "A new version is available!" msgstr "Hi ha una nova versió disponible!" msgid "Download" msgstr "Baixa" msgid "Could not get a session." msgstr "No s'ha pogut obtenir una sessió." msgid "Too many requests. Try again later." msgstr "Massa peticions. Proveu-ho de nou més tard." msgid "Not found." msgstr "No s'han trobat." msgid "Not Found." msgstr "No s'han trobat." msgid "..." msgstr "..." msgid "Search..." msgstr "Cerca..." msgid "Create..." msgstr "Crea..." #, python-format msgid "Create \"%s\"..." msgstr "Crea \"%s\"..." msgid "Value" msgstr "Valor" msgid "Displayed value" msgstr "Valor a mostrar" msgid "Format" msgstr "Format" msgid "Display format" msgstr "Format a mostrar" msgid "Open the calendar" msgstr "Obre el calendari" msgid "Date Format" msgstr "Format dates" msgid "Displayed date format" msgstr "Format dates a mostrar" msgid "y" msgstr "a" msgid "True" msgstr "Verdader" msgid "t" msgstr "v" msgid "False" msgstr "Fals" msgid "Digits" msgstr "Dígits" msgid "The number of decimal" msgstr "El número de decimals" msgid "Template" msgstr "Plantilla" msgid "Edit..." msgstr "Edita..." msgid "View Logs..." msgstr "Veure registres..." msgid "Attachments..." msgstr "Adjunts..." msgid "Notes..." msgstr "Notes..." msgid "Actions..." msgstr "Accions..." msgid "Relate..." msgstr "Relacionat..." msgid "Report..." msgstr "Informe..." msgid "Print..." msgstr "Imprimeix..." msgid "E-Mail..." msgstr "Correu electrònic..." msgid "Bold" msgstr "Negreta" msgid "Italic" msgstr "Cursiva" msgid "Underline" msgstr "Subratllat" msgid "Align Left" msgstr "Alinear a l'esquerra" msgid "Align Center" msgstr "Alinear al centre" msgid "Align Right" msgstr "Alinear a la dreta" msgid "Justify" msgstr "Justificar" msgid "Foreground Color" msgstr "Color de fons" msgid "Select a color" msgstr "Seleccioneu un color" msgid "Y" msgstr "A" msgid "M" msgstr "M" msgid "w" msgstr "S" msgid "d" msgstr "d" msgid "h" msgstr "h" msgid "m" msgstr "m" msgid "s" msgstr "s" msgid "Preferences..." msgstr "Preferències..." msgid "Toolbar" msgstr "Barra d'eines" msgid "Default" msgstr "Per defecte" msgid "Text and Icons" msgstr "Text i icones" msgid "Text" msgstr "Text" msgid "Icons" msgstr "Icones" msgid "Form" msgstr "Formulari" msgid "Save Column Width" msgstr "Guarda l'ample de la columna" msgid "Save Tree State" msgstr "Desa l'estat de l'arbre" msgid "Spell Checking" msgstr "Correcció ortogràfica" msgid "Play Sound for Code Scanner" msgstr "Activa el so del escàner de codis" msgid "PDA Mode" msgstr "Mode PDA" msgid "Search Limit..." msgstr "_Límit de cerca..." msgid "Check Version" msgstr "Comprova versió" msgid "Options" msgstr "Opcions" msgid "Documentation..." msgstr "Documentació..." msgid "Keyboard Shortcuts..." msgstr "Combinacions de tecles..." msgid "About..." msgstr "Quant a..." msgid "Help" msgstr "Ajuda" msgid "No result found." msgstr "No s'han trobat resultats." msgid "Favorites" msgstr "Preferits" msgid "Manage..." msgstr "Gestiona..." msgid "Action" msgstr "Acció" msgid "" "The following action requires to close all tabs.\n" "Do you want to continue?" msgstr "" "L'acció seleccionada requereix tancar totes les pestanyes.\n" "Voleu continuar?" msgid "Application Shortcuts" msgstr "Dreceres de l'aplicació" msgid "Global" msgstr "Global" msgid "Preferences" msgstr "Preferències" msgid "Search menu" msgstr "Menú de cerca" msgid "Toggle menu" msgstr "Commuta el menú" msgid "Previous tab" msgstr "Pestanya anterior" msgid "Next tab" msgstr "Pestanya següent" msgid "Shortcuts" msgstr "Dreceres" msgid "Quit" msgstr "Sortir" msgid "Edition Shortcuts" msgstr "Accessos directes a l'edició" msgid "Text Entries" msgstr "Entrades de text" msgid "Cut selected text" msgstr "Talla el text seleccionat" msgid "Copy selected text" msgstr "Copia el text seleccionat" msgid "Paste copied text" msgstr "Enganxa el text seleccionat" msgid "Next entry" msgstr "Següent entrada" msgid "Previous entry" msgstr "Entrada anterior" msgid "Relation Entries" msgstr "Entrades de relacions" msgid "Create new relation" msgstr "Crea una nova relació" msgid "Open/Search relation" msgstr "Obre/Cerca una relació" msgid "List Entries" msgstr "Llistat d'entrades" msgid "Switch view" msgstr "Canvia la vista" msgid "Create/Select new line" msgstr "Crear/Seleccionar línia nova" msgid "Open relation" msgstr "Obre una relació" msgid "Mark line for deletion/removal" msgstr "Marcar línia per eliminar/suprimir" msgid "Mark line for removal" msgstr "Marcar línia per eliminar" msgid "Unmark line for deletion" msgstr "Desmarca la línia per ser eliminada" msgid "List/Tree Shortcuts" msgstr "Drecera de llista/arbre" msgid "Move Cursor" msgstr "Mou cursor" msgid "Move right" msgstr "Mou a la dreta" msgid "Move left" msgstr "Mou a l'esquerra" msgid "Move up" msgstr "Mou amunt" msgid "Move down" msgstr "Mou avall" msgid "Move up of one page" msgstr "Mou amunt una pàgina" msgid "Move down of one page" msgstr "Mou avall una pàgina" msgid "Move to top" msgstr "Mou al principi" msgid "Move to bottom" msgstr "Mou al final" msgid "Move to parent" msgstr "Mou al pare" msgid "Edition" msgstr "Edita" msgid "Copy selected rows" msgstr "Copia les files seleccionades" msgid "Insert copied rows" msgstr "Inserta les files copiades" msgid "Select all" msgstr "Selecciona tot" msgid "Unselect all" msgstr "Deselecciona tot" msgid "Select parent" msgstr "Selecciona pare" msgid "Select/Activate current row" msgstr "Selecciona/Activa fila actual" msgid "Toggle selection" msgstr "Commuta selecció" msgid "Expand/Collapse" msgstr "Expandeix/Contrau" msgid "Expand row" msgstr "Expandeix fila" msgid "Collapse row" msgstr "Contrau fila" msgid "Toggle row" msgstr "Commuta fila" msgid "Collapse all rows" msgstr "Contrau totes les files" msgid "Expand all rows" msgstr "Expandeix totes les files" msgid "Close Tab" msgstr "Tanca pestanya" msgid "modularity, scalability and security" msgstr "modularitat, escalabilitat i seguretat" msgid "translator-credits" msgstr "Sergi Almacellas Abellana" #, python-format msgid "Attachments (%s)" msgstr "Adjunts (%s)" msgid "Code Scanner" msgstr "Escàner de codis" msgid "Code" msgstr "Codi" msgid "Profile Editor" msgstr "Editor de perfils" msgid "Profile" msgstr "Perfil" msgid "Add new profile" msgstr "Afegeix un nou perfil" msgid "Remove selected profile" msgstr "Elimina el perfil seleccionat" msgid "Host:" msgstr "Servidor:" msgid "Database:" msgstr "Base de dades:" msgid "Fetching databases list" msgstr "Recuperant llista de bases de dades" msgid "Username:" msgstr "Nom d'usuari:" msgid "Incompatible version of the server." msgstr "El client no és compatible amb la versió del servidor." msgid "Could not connect to the server." msgstr "No s'ha pogut connectar amb el servidor." msgid "Login" msgstr "Accedeix" msgid "_Cancel" msgstr "_Cancel·la" msgid "Cancel connection to the Tryton server" msgstr "Cancel·la connexió amb el servidor Tryton" msgid "C_onnect" msgstr "C_onnecta" msgid "Connect the Tryton server" msgstr "Connecta amb el servidor Tryton" msgid "Profile:" msgstr "Perfil:" msgid "Host / Database information" msgstr "Informació del Servidor / Base de dades" msgid "User name:" msgstr "Nom d'usuari:" msgid "Unable to complete email entry" msgstr "No s'ha pogut completar l'entrada del correu electrònic" #, python-format msgid "E-mail %s" msgstr "Correu electrònic %s" msgid "To:" msgstr "A:" msgid "Cc:" msgstr "Cc:" msgid "Bcc:" msgstr "Cco:" msgid "Subject:" msgstr "Assumpte:" msgid "Body" msgstr "Missatge" msgid "Reports" msgstr "Informes" msgid "Attachments" msgstr "Adjunts" msgid "Files" msgstr "Fitxers" msgid "Send" msgstr "Envia" msgid "Select File" msgstr "Selecciona un fitxer" msgid "Remove File" msgstr "Elimina fitxer" msgid "Select" msgstr "Selecciona" msgid "Add..." msgstr "Afegeix..." msgid "Preview" msgstr "Previsualitza" msgid "Previous" msgstr "Anterior" msgid "Next" msgstr "Següent" #, python-format msgid "Attachment (%s)" msgstr "Adjunts (%s)" #, python-format msgid "Note (%d/%d)" msgstr "Nota (%d/%d)" msgid "You have to select one record." msgstr "Ha de seleccionar un registre." msgid "Are you sure to remove this record?" msgstr "Esteu segur que voleu eliminar aquest registre?" msgid "Are you sure to remove those records?" msgstr "Esteu segur que voleu eliminar aquests registres?" msgid "Records not removed." msgstr "Els registres no s'han eliminat." msgid "Records removed." msgstr "Registres eliminats." msgid "Working now on the duplicated record(s)." msgstr "Està treballant amb un registre(s) duplicat." msgid "Record saved." msgstr "Registre desat." msgid "" "This record has been modified\n" "do you want to save it?" msgstr "" "Aquest registre ha estat modificat.\n" "Voleu desar-lo?" msgid "Launch action" msgstr "Executa acció" msgid "Relate" msgstr "Relacionat" msgid "Open related records" msgstr "Obre registres relacionats" msgid "Report" msgstr "Informes" msgid "Open report" msgstr "Obre informe" msgid "Print" msgstr "Imprimeix" msgid "Print report" msgstr "Imprimeix informe" msgid "_Copy URL" msgstr "_Copia l'URL" msgid "Copy URL into clipboard" msgstr "Copia l'URL al porta-retalls" msgid "Unknown" msgstr "Desconegut" msgid "Limit" msgstr "Límit" msgid "Search Limit Settings" msgstr "Preferències del límit de cerca" msgid "Limit:" msgstr "Límit:" #, python-format msgid "Logs (%s)" msgstr "Registres (%s)" msgid "Model:" msgstr "Model:" msgid "ID:" msgstr "Identificador:" msgid "Created by:" msgstr "Creat per:" msgid "Created at:" msgstr "Creat el:" msgid "Last Modified by:" msgstr "Última modificació per:" msgid "Last Modified at:" msgstr "Última modificació el:" #, python-format msgid "Notes (%s)" msgstr "Notes (%s)" msgid "Edit User Preferences" msgstr "Edita preferències d'usuari" msgid "Preference" msgstr "Preferències" msgid "Revision" msgstr "Revisió" msgid "Select a revision" msgstr "Seleccioneu una revisió" msgid "Revision:" msgstr "Revisió:" msgid "_Switch View" msgstr "Canvia la vi_sta" msgid "Switch View" msgstr "Canvia la vista" msgid "_Previous" msgstr "_Anterior" msgid "Previous Record" msgstr "Registre anterior" msgid "_Next" msgstr "Seg_üent" msgid "Next Record" msgstr "Registre següent" msgid "_Search" msgstr "_Cerca" msgid "_New" msgstr "_Nou" msgid "Create a new record" msgstr "Crea un nou registre" msgid "_Save" msgstr "_Desa" msgid "Save this record" msgstr "Desa aquest registre" msgid "_Reload/Undo" msgstr "_Recarrega/Desfés" msgid "Reload/Undo" msgstr "Recarrega/Desfés" msgid "_Duplicate" msgstr "_Duplica" msgid "_Delete..." msgstr "_Elimina..." msgid "View _Logs..." msgstr "Veure _registre..." msgid "Show revisions..." msgstr "Mostra revisions..." msgid "A_ttachments..." msgstr "Adjun_ts..." msgid "Add an attachment to the record" msgstr "Afegeix un adjunt al registre" msgid "_Notes..." msgstr "_Notes..." msgid "Add a note to the record" msgstr "Afegeix una nota al registre" msgid "_Actions..." msgstr "_Accions..." msgid "_Relate..." msgstr "_Relacionat..." msgid "_Report..." msgstr "_Informes..." msgid "_Print..." msgstr "Im_primeix..." msgid "_E-Mail..." msgstr "_Correu electrònic..." msgid "Send an e-mail using the record" msgstr "Envia un correu electrònic utilitzant el registre" msgid "_Export Data..." msgstr "_Exporta dades..." msgid "_Import Data..." msgstr "_Importa dades..." msgid "Copy _URL..." msgstr "Copia l'_URL..." msgid "_Close Tab" msgstr "_Tanca la pestanya" msgid "All fields" msgstr "Tots els camps" msgid "_Add" msgstr "_Afegeix" msgid "_Remove" msgstr "_Elimina" msgid "_Clear" msgstr "_Neteja" msgid "Fields selected" msgstr "Camps seleccionats" msgid "CSV Parameters" msgstr "Paràmetres CSV" msgid "Delimiter:" msgstr "Separador:" msgid "Quote char:" msgstr "Delimitador de text:" msgid "Encoding:" msgstr "Codificació:" msgid "Use locale format" msgstr "Utilitza format local" msgid "Field name" msgstr "Nom del camp" #, python-format msgid "CSV Export: %s" msgstr "Exportació CSV: %s" msgid "_Save Export" msgstr "_Desa exportació" msgid "_URL Export" msgstr "_URL exportació" msgid "_Delete Export" msgstr "_Elimina exportació" msgid "Predefined exports" msgstr "Exportacions predeterminades" msgid "Name" msgstr "Nom" msgid "Open" msgstr "Obre" msgid "Save" msgstr "Desa" msgid "Listed Records" msgstr "Registres llistats" msgid "Selected Records" msgstr "Registres seleccionats" msgid "Ignore search limit" msgstr "Ignora límit de cerca" msgid "Add field names" msgstr "Afegeix noms de camp" #, python-format msgid "%s (string)" msgstr "%s (text)" #, python-format msgid "%s (model name)" msgstr "%s (nom del model)" #, python-format msgid "%s/Record Name" msgstr "%s/Nom del registre" msgid "What is the name of this export?" msgstr "Quin és el nom d'aquesta exportació?" #, python-format msgid "Override '%s' definition?" msgstr "Voleu sobreescriure la definició de '%s'?" #, python-format msgid "%d record saved." msgstr "S'ha desat %d registre." #, python-format msgid "%d records saved." msgstr "S'han desat %d registres." msgid "Export failed" msgstr "Ha fallat l'exportació" msgid "Link" msgstr "Enllaç" msgid "Delete" msgstr "Elimina" msgid "Discard changes" msgstr "Descartar els canvis" msgid "Save and New" msgstr "Guarda i nou" msgid "Add and New" msgstr "Afegeix i nou" msgid "Add" msgstr "Afegeix" msgid "Apply changes" msgstr "Aplica canvis" msgid "Switch" msgstr "Canvia la vista" msgid "Remove " msgstr "Elimina " msgid "Create a new record " msgstr "Crea un nou registre " msgid "Delete selected record " msgstr "Elimina el registre seleccionat " msgid "Undelete selected record " msgstr "Recupera el registre seleccionat " #, python-format msgid "CSV Import: %s" msgstr "Importació CSV: %s" msgid "_Auto-Detect" msgstr "Detecta _automàticament" msgid "File to Import:" msgstr "Fitxer a importar:" msgid "Open..." msgstr "Obre..." msgid "Lines to Skip:" msgstr "Línies a ometre:" msgid "You must select an import file first." msgstr "Primer heu de seleccionar un fitxer a importar." msgid "Detection failed" msgstr "Ha fallat la detecció" #, python-format msgid "Unknown column header \"%s\"" msgstr "Capçalera de columan desconeguda: \"%s\"" msgid "Error" msgstr "Error" msgid "Import failed" msgstr "Ha fallat la importació" #, python-format msgid "%d record imported." msgstr "S'ha importat %d registre." #, python-format msgid "%d records imported." msgstr "S'han importat %d registres." msgid "Search" msgstr "Cerca" msgid "New" msgstr "Nou" #, python-format msgid "Search %s" msgstr "Cerca %s" msgid "Wizard" msgstr "Assistent" msgid "ID" msgstr "ID" msgid "Created by" msgstr "Creat per" msgid "Created at" msgstr "Creat el" msgid "Edited by" msgstr "Editat per" msgid "Edited at" msgstr "Editat el" msgid "Unable to set view tree state" msgstr "No s'ha pogut desar l'estat de la vista d'arbre" #, python-format msgid "\"%s\" is not valid according to its domain." msgstr "\"%s\" no és vàlid segons el seu domini." #, python-format msgid "\"%s\" is required." msgstr "\"%s\" és obligatori." #, python-format msgid "The values of \"%s\" are not valid." msgstr "Els valors de \"%s\" no són vàlids." msgid "Pre-validation" msgstr "Prevalidació" msgid ":" msgstr ":" msgid "Scan" msgstr "Escaneja" msgid "Image Size" msgstr "Mida de la imatge" msgid "Width:" msgstr "Amplada:" msgid "Height:" msgstr "Altura:" msgid "PNG image (*.png)" msgstr "Imatge PNG (*.png)" msgid "Save As" msgstr "Anomena i desa" msgid "Image size too large." msgstr "La mida de la imatge és massa gran." msgid "Copy" msgstr "Copia" msgid "Paste" msgstr "Enganxa" msgid ".." msgstr ".." msgid "Open filters" msgstr "Obre filtres" msgid "Show bookmarks of filters" msgstr "Mostra les cerques preferides" msgid "Remove this bookmark" msgstr "Elimina de les cerques preferides" msgid "Bookmark this filter" msgstr "Desa com a cerca preferida" msgid "Show active records" msgstr "Mostra registres actius" msgid "Show inactive records" msgstr "Mostra registres inactius" msgid "Bookmark Name:" msgstr "Nom de la cerca preferida:" msgid "Find" msgstr "Cerca" msgid "Today" msgstr "Avui" msgid "go back" msgstr "vés enrere" msgid "go forward" msgstr "vés endavant" msgid "previous year" msgstr "any anterior" msgid "next year" msgstr "l'any següent" msgid "Day" msgstr "Dia" msgid "Week" msgstr "Setmana" msgid "Month" msgstr "Mes" msgid "Select..." msgstr "Selecciona..." msgid "Clear" msgstr "Neteja" msgid "All files" msgstr "Tots els fitxers" msgid "Show plain text" msgstr "Mostra com a text pla" msgid "Add value" msgstr "Afegeix un valor" #, python-format msgid "Remove \"%s\"" msgstr "Eliminar \"%s\"" msgid "Images" msgstr "Imatges" msgid "Add existing record" msgstr "Afegeix un registre existent" msgid "Remove selected record" msgstr "Elimina el registre seleccionat" msgid "Open the record " msgstr "Obre el registre " msgid "Clear the field " msgstr "Eliminar el registre " msgid "Search a record " msgstr "Cerca un registre " msgid "Edit selected record" msgstr "Edita registre seleccionat" msgid "Delete selected record" msgstr "Elimina el registre seleccionat" #, python-format msgid "%s%%" msgstr "%s%%" msgid "Choose a language" msgstr "Escull un idioma" msgid "Translation" msgstr "Traducció" msgid "Edit" msgstr "Edita" msgid "Fuzzy" msgstr "Dubtosa" msgid "You need to save the record before adding translations." msgstr "Heu de desar el registre abans d'afegir traduccions." msgid "No other language available." msgstr "No hi ha cap altre idioma disponible." msgid "#ERROR" msgstr "#ERROR" msgid "Translate view" msgstr "Tradueix la vista" ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1745680285.1241102 tryton-7.0.24/tryton/data/locale/cs/0000755000175000017500000000000015003173635015372 5ustar00cedced././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1745680285.1607773 tryton-7.0.24/tryton/data/locale/cs/LC_MESSAGES/0000755000175000017500000000000015003173635017157 5ustar00cedced././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1745680279.0 tryton-7.0.24/tryton/data/locale/cs/LC_MESSAGES/tryton.mo0000644000175000017500000001103215003173627021051 0ustar00cedced^   :AE eo#%& $,B\o    $38>EI NZ_g|       ) 8 A H T X g l         * / 5 ? I V ^ d q   S f h }     + ' ', T b "m        . DO V w   6 @Mh}     %- @ KUfl~    $ 4 BN^qx   +%s/Record Name:All fieldsPredefined exportsActionAddAdd an attachment to the recordAll filesAlways ignore this warning.Are you sure to remove this record?Are you sure to remove those records?CSV ParametersC_onnectCancel connection to the Tryton serverClearClose TabCompareConcurrency ExceptionConnect the Tryton serverCopy selected textCreate a new recordCreate new relationCut selected textDatabase:DeleteEdit User PreferencesEncoding:ErrorFalseField nameFile to Import:FindHeight:ID:Image SizeImagesLines to Skip:LinkLoginModel:NewNextNext RecordOpenOpen...Open/Search relationPNG image (*.png)Paste copied textPreferencePreferencesPreviousPrevious RecordPrintReport BugSave AsSave As...Save this recordSearchSelect your actionSelectionSpell CheckingSubject:SwitchSwitch viewTo:Translate viewTrueUnable to set locale %sUnknownUser name:View _Logs...Width:WizardWrite AnywayYour selection:_Actions..._Add_Cancel_Close Tab_Delete..._Duplicate_Export Data..._Import Data..._New_Next_Previous_Print..._Reload/Undo_Remove_Save_Switch Viewspecify alternate config filespecify the login userProject-Id-Version: PROJECT VERSION Report-Msgid-Bugs-To: EMAIL@ADDRESS POT-Creation-Date: 2025-04-26 17:11+0200 PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE Last-Translator: FULL NAME Language: cs Language-Team: cs Plural-Forms: nplurals=3; plural=((n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2); MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: 8bit Generated-By: Babel 2.17.0 %s/Název záznamu:Všechna polePředdefinované exportyAkcePřidatPřidat přílohu k záznamuVšechny souboryzadejte alternativní konfigurační souborOpravdu chcete odstranit tento záznam?Opravdu chcete odstranit tyto záznamy?Parametry CSV_PřipojitZrušit spojení k Tryton serveru.VymazatZavřít záložkuPorovnatVýjimka při souběhuPřipojit k Tryton serveruKopírovat vybraný textVytvořit nový záznamVytvořit novou relaciVyjmout vybraný textDatabáze:SmazatUpravit uživatelské nastaveníKódování:ChybaNepravdaJméno poleSoubor k importu:NajítVýška:ID:Velikost obrázkuObrázkyVynechané řádky:OdkazPřihlášeníModel:_NovýDalšíNásledující záznamOtevřítOtevřít...Otevřít/prohledat relaciObrázek PNG (*.png)Vložit vybraný textNastaveníNastaveníPředchozíPředchozí záznamTiskNahlásit chybuUložit jakoUložit jako...Uložit tento záznamHledatVyberte akciVýběrKontrola pravopisuPředmět:PřepnoutPřepnout pohledKomu:Přeložit pohledPravdaNemohu nastavit lokalizaci %sNeznámýUživatelské jméno:Zobrazit záznamy...Šířka:PrůvodcePřesto zapsatVáš výběr:Akce..._Přidat_Zrušit_Zavřít kartu_Odstranit..._DuplikovatExportovat dataImportovat data..._Nový_Následující_Předchozí_Tisk...Obnovit/Zpět_Odstranit_UložitPřepnout pohledzadejte alternativní konfigurační souborzadejte uživatelské jméno././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685599.0 tryton-7.0.24/tryton/data/locale/cs/LC_MESSAGES/tryton.po0000644000175000017500000004330214517761237021072 0ustar00cedced# Translations template for tryton. # Copyright (C) 2016 Tryton # This file is distributed under the same license as the tryton project. # FIRST AUTHOR , 2016. msgid "" msgstr "Content-Type: text/plain; charset=utf-8\n" msgid "specify alternate config file" msgstr "zadejte alternativní konfigurační soubor" msgid "development mode" msgstr "" msgid "logging everything at INFO level" msgstr "" msgid "specify the log level: DEBUG, INFO, WARNING, ERROR, CRITICAL" msgstr "" msgid "specify the file used to output logging information" msgstr "" msgid "specify the login user" msgstr "zadejte uživatelské jméno" #, fuzzy msgid "specify the server hostname:port" msgstr "zadejte název hostitele pro server" msgid "disable thread usage" msgstr "" #, python-format msgid "Unable to set locale %s" msgstr "Nemohu nastavit lokalizaci %s" #, fuzzy msgid ", " msgstr ", " msgid ",..." msgstr "" #, python-format msgid "%s (%s)" msgstr "" msgid "Select your action" msgstr "Vyberte akci" #, fuzzy msgid "No action defined." msgstr "Žádná akce není definována!" msgid "By: " msgstr "" msgid "Selection" msgstr "Výběr" #, fuzzy msgid "Cancel" msgstr "_Zrušit" msgid "OK" msgstr "" msgid "Your selection:" msgstr "Váš výběr:" msgid "Save As..." msgstr "Uložit jako..." msgid "Do you want to proceed?" msgstr "" msgid "Always ignore this warning." msgstr "zadejte alternativní konfigurační soubor" msgid "No" msgstr "" msgid "Yes" msgstr "" msgid "Concurrency Exception" msgstr "Výjimka při souběhu" msgid "This record has been modified while you were editing it." msgstr "" msgid "Cancel saving" msgstr "" msgid "Compare" msgstr "Porovnat" msgid "See the modified version" msgstr "" msgid "Write Anyway" msgstr "Přesto zapsat" msgid "Save your current version" msgstr "" #, fuzzy, python-format msgid "Compare: %s" msgstr "Porovnat: %s" msgid "Close" msgstr "" #, fuzzy msgid "Application Error" msgstr "Chyba aplikace!" msgid "Report Bug" msgstr "Nahlásit chybu" #, python-format msgid "Check URL: %s" msgstr "" msgid "Unable to check for new version." msgstr "" msgid "A new version is available!" msgstr "" msgid "Download" msgstr "" #, fuzzy msgid "Could not get a session." msgstr "Nelze se spojit se serverem!" msgid "Too many requests. Try again later." msgstr "" msgid "Not found." msgstr "" msgid "Not Found." msgstr "" #, fuzzy msgid "..." msgstr "..." msgid "Search..." msgstr "" msgid "Create..." msgstr "" #, python-format msgid "Create \"%s\"..." msgstr "" #, fuzzy msgid "Value" msgstr "Nepravda" msgid "Displayed value" msgstr "" #, fuzzy msgid "Format" msgstr "_Normální" msgid "Display format" msgstr "" #, fuzzy msgid "Open the calendar" msgstr "Otevřít kalendář" msgid "Date Format" msgstr "" msgid "Displayed date format" msgstr "" msgid "y" msgstr "" msgid "True" msgstr "Pravda" msgid "t" msgstr "" msgid "False" msgstr "Nepravda" msgid "Digits" msgstr "" msgid "The number of decimal" msgstr "" #, fuzzy msgid "Template" msgstr "_Vytvořit" #, fuzzy msgid "Edit..." msgstr "Konec..." #, fuzzy msgid "View Logs..." msgstr "Zobrazit záznamy..." #, fuzzy msgid "Attachments..." msgstr "Příloha:" msgid "Notes..." msgstr "" #, fuzzy msgid "Actions..." msgstr "Akce..." #, fuzzy msgid "Relate..." msgstr "_Odstranit..." #, fuzzy msgid "Report..." msgstr "Importovat data..." #, fuzzy msgid "Print..." msgstr "_Tisk..." #, fuzzy msgid "E-Mail..." msgstr "_Email..." msgid "Bold" msgstr "" msgid "Italic" msgstr "" msgid "Underline" msgstr "" msgid "Align Left" msgstr "" msgid "Align Center" msgstr "" msgid "Align Right" msgstr "" msgid "Justify" msgstr "" msgid "Foreground Color" msgstr "" msgid "Select a color" msgstr "" msgid "Y" msgstr "" msgid "M" msgstr "" msgid "w" msgstr "" msgid "d" msgstr "" msgid "h" msgstr "" msgid "m" msgstr "" msgid "s" msgstr "" #, fuzzy msgid "Preferences..." msgstr "_Vlastnosti..." #, fuzzy msgid "Toolbar" msgstr "Nástrojová lišta" #, fuzzy msgid "Default" msgstr "_Výchozí" #, fuzzy msgid "Text and Icons" msgstr "_Text a ikony" #, fuzzy msgid "Text" msgstr "_Text" #, fuzzy msgid "Icons" msgstr "_Ikony" #, fuzzy msgid "Form" msgstr "Formulář" msgid "Save Column Width" msgstr "" msgid "Save Tree State" msgstr "" msgid "Spell Checking" msgstr "Kontrola pravopisu" msgid "Play Sound for Code Scanner" msgstr "" msgid "PDA Mode" msgstr "" msgid "Search Limit..." msgstr "" msgid "Check Version" msgstr "" #, fuzzy msgid "Options" msgstr "_Volby" #, fuzzy msgid "Documentation..." msgstr "Akce..." #, fuzzy msgid "Keyboard Shortcuts..." msgstr "_Klávesové zkratky..." #, fuzzy msgid "About..." msgstr "_O programu..." #, fuzzy msgid "Help" msgstr "Nápo_věda" msgid "No result found." msgstr "" msgid "Favorites" msgstr "" msgid "Manage..." msgstr "" msgid "Action" msgstr "Akce" msgid "" "The following action requires to close all tabs.\n" "Do you want to continue?" msgstr "" msgid "Application Shortcuts" msgstr "" msgid "Global" msgstr "" msgid "Preferences" msgstr "Nastavení" msgid "Search menu" msgstr "" msgid "Toggle menu" msgstr "" #, fuzzy msgid "Previous tab" msgstr "_Předchozí" msgid "Next tab" msgstr "" msgid "Shortcuts" msgstr "" msgid "Quit" msgstr "" msgid "Edition Shortcuts" msgstr "" msgid "Text Entries" msgstr "" msgid "Cut selected text" msgstr "Vyjmout vybraný text" msgid "Copy selected text" msgstr "Kopírovat vybraný text" msgid "Paste copied text" msgstr "Vložit vybraný text" msgid "Next entry" msgstr "" msgid "Previous entry" msgstr "" msgid "Relation Entries" msgstr "" msgid "Create new relation" msgstr "Vytvořit novou relaci" msgid "Open/Search relation" msgstr "Otevřít/prohledat relaci" msgid "List Entries" msgstr "" msgid "Switch view" msgstr "Přepnout pohled" msgid "Create/Select new line" msgstr "" #, fuzzy msgid "Open relation" msgstr "Otevřít/prohledat relaci" msgid "Mark line for deletion/removal" msgstr "" msgid "Mark line for removal" msgstr "" msgid "Unmark line for deletion" msgstr "" msgid "List/Tree Shortcuts" msgstr "" msgid "Move Cursor" msgstr "" msgid "Move right" msgstr "" msgid "Move left" msgstr "" msgid "Move up" msgstr "" msgid "Move down" msgstr "" msgid "Move up of one page" msgstr "" msgid "Move down of one page" msgstr "" msgid "Move to top" msgstr "" msgid "Move to bottom" msgstr "" msgid "Move to parent" msgstr "" #, fuzzy msgid "Edition" msgstr "Akce" #, fuzzy msgid "Copy selected rows" msgstr "Kopírovat vybraný text" msgid "Insert copied rows" msgstr "" msgid "Select all" msgstr "" msgid "Unselect all" msgstr "" msgid "Select parent" msgstr "" msgid "Select/Activate current row" msgstr "" msgid "Toggle selection" msgstr "" msgid "Expand/Collapse" msgstr "" msgid "Expand row" msgstr "" msgid "Collapse row" msgstr "" msgid "Toggle row" msgstr "" msgid "Collapse all rows" msgstr "" msgid "Expand all rows" msgstr "" msgid "Close Tab" msgstr "Zavřít záložku" msgid "modularity, scalability and security" msgstr "" msgid "translator-credits" msgstr "" #, fuzzy, python-format msgid "Attachments (%s)" msgstr "Příloha(%s)" msgid "Code Scanner" msgstr "" msgid "Code" msgstr "" msgid "Profile Editor" msgstr "" msgid "Profile" msgstr "" msgid "Add new profile" msgstr "" msgid "Remove selected profile" msgstr "" #, fuzzy msgid "Host:" msgstr "Port:" msgid "Database:" msgstr "Databáze:" #, fuzzy msgid "Fetching databases list" msgstr "Nastavení nové databáze:" #, fuzzy msgid "Username:" msgstr "Uživatelské jméno:" #, fuzzy msgid "Incompatible version of the server." msgstr "Verze serveru není kompatibilní!" #, fuzzy msgid "Could not connect to the server." msgstr "Nelze se spojit se serverem!" msgid "Login" msgstr "Přihlášení" msgid "_Cancel" msgstr "_Zrušit" msgid "Cancel connection to the Tryton server" msgstr "Zrušit spojení k Tryton serveru." msgid "C_onnect" msgstr "_Připojit" msgid "Connect the Tryton server" msgstr "Připojit k Tryton serveru" msgid "Profile:" msgstr "" msgid "Host / Database information" msgstr "" msgid "User name:" msgstr "Uživatelské jméno:" #, fuzzy msgid "Unable to complete email entry" msgstr "Nemohu nastavit lokalizaci %s" #, fuzzy, python-format msgid "E-mail %s" msgstr "Email %s" msgid "To:" msgstr "Komu:" #, fuzzy msgid "Cc:" msgstr "CC:" msgid "Bcc:" msgstr "" msgid "Subject:" msgstr "Předmět:" #, fuzzy msgid "Body" msgstr "Tělo:" #, fuzzy msgid "Reports" msgstr "Sestavy" #, fuzzy msgid "Attachments" msgstr "Příloha:" msgid "Files" msgstr "" msgid "Send" msgstr "" #, fuzzy msgid "Select File" msgstr "Výběr" #, fuzzy msgid "Remove File" msgstr "_Odstranit" msgid "Select" msgstr "" msgid "Add..." msgstr "" #, fuzzy msgid "Preview" msgstr "Předchozí" msgid "Previous" msgstr "Předchozí" msgid "Next" msgstr "Další" #, fuzzy, python-format msgid "Attachment (%s)" msgstr "Příloha(%s)" #, python-format msgid "Note (%d/%d)" msgstr "" #, fuzzy msgid "You have to select one record." msgstr "Musíte vybrat právě jeden záznam!" msgid "Are you sure to remove this record?" msgstr "Opravdu chcete odstranit tento záznam?" msgid "Are you sure to remove those records?" msgstr "Opravdu chcete odstranit tyto záznamy?" #, fuzzy msgid "Records not removed." msgstr "Záznamy neodstraněny!" #, fuzzy msgid "Records removed." msgstr "Záznamy odstraněny!" #, fuzzy msgid "Working now on the duplicated record(s)." msgstr "Nyní pracujete s duplicitním záznamem!" #, fuzzy msgid "Record saved." msgstr "Záznam uložen!" #, fuzzy msgid "" "This record has been modified\n" "do you want to save it?" msgstr "" "Tento záznam byl upraven\n" "chcete ho uložit?" msgid "Launch action" msgstr "" #, fuzzy msgid "Relate" msgstr "_Vytvořit" #, fuzzy msgid "Open related records" msgstr "Otevřít záznam" #, fuzzy msgid "Report" msgstr "Sestavy" #, fuzzy msgid "Open report" msgstr "Otevřít záznam" msgid "Print" msgstr "Tisk" msgid "Print report" msgstr "" msgid "_Copy URL" msgstr "" msgid "Copy URL into clipboard" msgstr "" msgid "Unknown" msgstr "Neznámý" #, fuzzy msgid "Limit" msgstr "Omezit:" msgid "Search Limit Settings" msgstr "" #, fuzzy msgid "Limit:" msgstr "Omezit:" #, python-format msgid "Logs (%s)" msgstr "" msgid "Model:" msgstr "Model:" msgid "ID:" msgstr "ID:" #, fuzzy msgid "Created by:" msgstr "Datum vytvoření:" #, fuzzy msgid "Created at:" msgstr "Datum vytvoření:" #, fuzzy msgid "Last Modified by:" msgstr "Poslední úpravu provedl:" #, fuzzy msgid "Last Modified at:" msgstr "Datum poslední úpravy:" #, python-format msgid "Notes (%s)" msgstr "" msgid "Edit User Preferences" msgstr "Upravit uživatelské nastavení" msgid "Preference" msgstr "Nastavení" #, fuzzy msgid "Revision" msgstr "Předchozí" #, fuzzy msgid "Select a revision" msgstr "Výběr" #, fuzzy msgid "Revision:" msgstr "Předchozí" msgid "_Switch View" msgstr "Přepnout pohled" #, fuzzy msgid "Switch View" msgstr "Přepnout pohled" msgid "_Previous" msgstr "_Předchozí" msgid "Previous Record" msgstr "Předchozí záznam" msgid "_Next" msgstr "_Následující" msgid "Next Record" msgstr "Následující záznam" #, fuzzy msgid "_Search" msgstr "Hledat" msgid "_New" msgstr "_Nový" msgid "Create a new record" msgstr "Vytvořit nový záznam" msgid "_Save" msgstr "_Uložit" msgid "Save this record" msgstr "Uložit tento záznam" msgid "_Reload/Undo" msgstr "Obnovit/Zpět" #, fuzzy msgid "Reload/Undo" msgstr "Obnovit/Zpět" msgid "_Duplicate" msgstr "_Duplikovat" msgid "_Delete..." msgstr "_Odstranit..." msgid "View _Logs..." msgstr "Zobrazit záznamy..." msgid "Show revisions..." msgstr "" #, fuzzy msgid "A_ttachments..." msgstr "Příloha:" msgid "Add an attachment to the record" msgstr "Přidat přílohu k záznamu" msgid "_Notes..." msgstr "" msgid "Add a note to the record" msgstr "" msgid "_Actions..." msgstr "Akce..." #, fuzzy msgid "_Relate..." msgstr "_Odstranit..." #, fuzzy msgid "_Report..." msgstr "Importovat data..." msgid "_Print..." msgstr "_Tisk..." #, fuzzy msgid "_E-Mail..." msgstr "_Email..." #, fuzzy msgid "Send an e-mail using the record" msgstr "Uložit tento záznam" msgid "_Export Data..." msgstr "Exportovat data" msgid "_Import Data..." msgstr "Importovat data..." msgid "Copy _URL..." msgstr "" msgid "_Close Tab" msgstr "_Zavřít kartu" msgid "All fields" msgstr "Všechna pole" msgid "_Add" msgstr "_Přidat" msgid "_Remove" msgstr "_Odstranit" #, fuzzy msgid "_Clear" msgstr "Vymazat" msgid "Fields selected" msgstr "" msgid "CSV Parameters" msgstr "Parametry CSV" msgid "Delimiter:" msgstr "" msgid "Quote char:" msgstr "" msgid "Encoding:" msgstr "Kódování:" msgid "Use locale format" msgstr "" msgid "Field name" msgstr "Jméno pole" #, python-format msgid "CSV Export: %s" msgstr "" #, fuzzy msgid "_Save Export" msgstr "Uložit exportu" #, fuzzy msgid "_URL Export" msgstr "Uložit exportu" #, fuzzy msgid "_Delete Export" msgstr "Smazat exportu" msgid "Predefined exports" msgstr "Předdefinované exporty" msgid "Name" msgstr "" msgid "Open" msgstr "Otevřít" #, fuzzy msgid "Save" msgstr "_Uložit" #, fuzzy msgid "Listed Records" msgstr "Upravit vybraný záznam" #, fuzzy msgid "Selected Records" msgstr "Upravit vybraný záznam" msgid "Ignore search limit" msgstr "" #, fuzzy msgid "Add field names" msgstr "Přidat jména polí" #, python-format msgid "%s (string)" msgstr "" #, python-format msgid "%s (model name)" msgstr "" #, python-format msgid "%s/Record Name" msgstr "%s/Název záznamu" msgid "What is the name of this export?" msgstr "" #, python-format msgid "Override '%s' definition?" msgstr "" #, fuzzy, python-format msgid "%d record saved." msgstr "%d záznam uložen!" #, fuzzy, python-format msgid "%d records saved." msgstr "%d záznamů uloženo!" msgid "Export failed" msgstr "" msgid "Link" msgstr "Odkaz" msgid "Delete" msgstr "Smazat" msgid "Discard changes" msgstr "" msgid "Save and New" msgstr "" msgid "Add and New" msgstr "" msgid "Add" msgstr "Přidat" msgid "Apply changes" msgstr "" msgid "Switch" msgstr "Přepnout" msgid "Remove " msgstr "" #, fuzzy msgid "Create a new record " msgstr "Vytvořit nový záznam" #, fuzzy msgid "Delete selected record " msgstr "Smazat vybraný záznam" #, fuzzy msgid "Undelete selected record " msgstr "Smazat vybraný záznam" #, python-format msgid "CSV Import: %s" msgstr "" #, fuzzy msgid "_Auto-Detect" msgstr "Automatická detekce" msgid "File to Import:" msgstr "Soubor k importu:" msgid "Open..." msgstr "Otevřít..." msgid "Lines to Skip:" msgstr "Vynechané řádky:" #, fuzzy msgid "You must select an import file first." msgstr "Nejprve je nutné vybrat soubor pro import!" msgid "Detection failed" msgstr "" #, python-format msgid "Unknown column header \"%s\"" msgstr "" msgid "Error" msgstr "Chyba" msgid "Import failed" msgstr "" #, fuzzy, python-format msgid "%d record imported." msgstr "%d záznam načten!" #, fuzzy, python-format msgid "%d records imported." msgstr "%d záznamů načteno!" msgid "Search" msgstr "Hledat" msgid "New" msgstr "_Nový" #, python-format msgid "Search %s" msgstr "" msgid "Wizard" msgstr "Průvodce" #, fuzzy msgid "ID" msgstr "Najít" #, fuzzy msgid "Created by" msgstr "Datum vytvoření:" #, fuzzy msgid "Created at" msgstr "Datum vytvoření:" msgid "Edited by" msgstr "" msgid "Edited at" msgstr "" #, fuzzy msgid "Unable to set view tree state" msgstr "Nemohu nastavit lokalizaci %s" #, python-format msgid "\"%s\" is not valid according to its domain." msgstr "" #, python-format msgid "\"%s\" is required." msgstr "" #, python-format msgid "The values of \"%s\" are not valid." msgstr "" msgid "Pre-validation" msgstr "" msgid ":" msgstr ":" msgid "Scan" msgstr "" msgid "Image Size" msgstr "Velikost obrázku" msgid "Width:" msgstr "Šířka:" msgid "Height:" msgstr "Výška:" msgid "PNG image (*.png)" msgstr "Obrázek PNG (*.png)" msgid "Save As" msgstr "Uložit jako" #, fuzzy msgid "Image size too large." msgstr "Rozměry obrázku jsou příliš velké!" msgid "Copy" msgstr "" msgid "Paste" msgstr "" msgid ".." msgstr "" msgid "Open filters" msgstr "" msgid "Show bookmarks of filters" msgstr "" msgid "Remove this bookmark" msgstr "" msgid "Bookmark this filter" msgstr "" msgid "Show active records" msgstr "" msgid "Show inactive records" msgstr "" msgid "Bookmark Name:" msgstr "" msgid "Find" msgstr "Najít" #, fuzzy msgid "Today" msgstr "Tělo:" msgid "go back" msgstr "" msgid "go forward" msgstr "" msgid "previous year" msgstr "" msgid "next year" msgstr "" msgid "Day" msgstr "" msgid "Week" msgstr "" #, fuzzy msgid "Month" msgstr "Přepnout pohled" msgid "Select..." msgstr "" msgid "Clear" msgstr "Vymazat" msgid "All files" msgstr "Všechny soubory" msgid "Show plain text" msgstr "" msgid "Add value" msgstr "" #, python-format msgid "Remove \"%s\"" msgstr "" msgid "Images" msgstr "Obrázky" #, fuzzy msgid "Add existing record" msgstr "Uložit tento záznam" #, fuzzy msgid "Remove selected record" msgstr "Smazat vybraný záznam" #, fuzzy msgid "Open the record " msgstr "Otevřít záznam" msgid "Clear the field " msgstr "" #, fuzzy msgid "Search a record " msgstr "Hledat záznam" #, fuzzy msgid "Edit selected record" msgstr "Upravit vybraný záznam" #, fuzzy msgid "Delete selected record" msgstr "Smazat vybraný záznam" #, python-format msgid "%s%%" msgstr "" msgid "Choose a language" msgstr "" #, fuzzy msgid "Translation" msgstr "Přidat překlad" msgid "Edit" msgstr "" msgid "Fuzzy" msgstr "" #, fuzzy msgid "You need to save the record before adding translations." msgstr "Musíte uložit záznam před přidáváním překladů!" #, fuzzy msgid "No other language available." msgstr "Jiný jazyk není k dispozici!" msgid "#ERROR" msgstr "" msgid "Translate view" msgstr "Přeložit pohled" ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1745680285.1241102 tryton-7.0.24/tryton/data/locale/de/0000755000175000017500000000000015003173635015355 5ustar00cedced././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1745680285.1607773 tryton-7.0.24/tryton/data/locale/de/LC_MESSAGES/0000755000175000017500000000000015003173635017142 5ustar00cedced././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1745680279.0 tryton-7.0.24/tryton/data/locale/de/LC_MESSAGES/tryton.mo0000644000175000017500000004713715003173627021053 0ustar00cedcedL |*}  %16EHMPTVh  ?K_o      #%4Z jv&  2@ D R`rx     0=P c    &2 D NZ^fm  !2 ; EOTj        & 1 A G L ] b i o v ~         #  !3!:!B!X!j! |!!!!! !!!! !! !!","3" 9" E"O" e" o"z"" """""" " """"#!# 2# =# H# U#`#i#l# q#~# # #######$$.$4$F$b$ q$ |$$$$$$ $$ $$$$%% % !%/%D%U% \%f% w% % % %%%%% % %&& &#&(& 0&;&M& ]&j&{&&& &&&& && ' ''-' ?' J'X' k'u'' ''' ''' ((/(A(P(Y( `( l(x(( ((I((!)5$)8Z))) ) ))#))) * * *2*Q*i** **** ** + + + &+ 3+A+ F+g+n+(u+ ++++%+7+., >,J, O,\,d, k, v,, , , ,,,,, , , , , -- -$- *-7- ?- L-X-Z-k-- -- --$- - ---3.<I.. ......4g0*00001191A1 ^1j1o11111111111! 2 /2<2E2 L2 X2)d2&22!2233+3:3M3_3 r3+3333031 4 R4^4 g4 u44 444)4444 4 5 5 5#575;5U5o555 555555 5 66'36[6d6666%6+6717K7j77 7 7 7 77 7 8888 %8(F8 o8|888888 8 9 9 *9 49 >9 I9"j9 9 99 99999:%:;:Q: X: b:: ::::::::::::;;; ; ";.;A;H;^;z;; ;;;;;;< < %< 2<'?<g<~< << <&<<<<<==1=D=X=%i====== === >!>&>!>>`>o>> > > >>>>#>>??&? =?H? a?+o??? ??$? @ @ &@4@E@ N@Y@n@~@@@ @@@@@@ @'A!?A aAkAzAAAAAA'AB5B =B KBWB `B lB yBBBBBBBC&C.C5C>CTCgC}CC CCCCC$D,D ;D$IDnDDD4DDDDE4EFE`EvEEEEEE EEOE)F"?F;bFPFFFFG+G9All fieldsFields selectedPredefined exportsCreate...Search...A new version is available!A_ttachments...About...ActionActions...AddAdd a note to the recordAdd an attachment to the recordAdd and NewAdd existing recordAdd field namesAdd new profileAdd valueAdd...Align CenterAlign LeftAlign RightAll filesAlways ignore this warning.Application ErrorApplication ShortcutsApply changesAre you sure to remove this record?Are you sure to remove those records?Attachment (%s)AttachmentsAttachments (%s)Attachments...Bcc:BodyBoldBookmark Name:Bookmark this filterBy: CSV Export: %sCSV Import: %sCSV ParametersC_onnectCancelCancel connection to the Tryton serverCancel savingCc:Check URL: %sCheck VersionChoose a languageClearClear the field CloseClose TabCodeCode ScannerCollapse all rowsCollapse rowCompareCompare: %sConcurrency ExceptionConnect the Tryton serverCopyCopy URL into clipboardCopy _URL...Copy selected rowsCopy selected textCould not connect to the server.Could not get a session.Create "%s"...Create a new recordCreate a new record Create new relationCreate/Select new lineCreated atCreated at:Created byCreated by:Cut selected textDatabase:Date FormatDayDefaultDeleteDelete selected recordDelete selected record Delimiter:Detection failedDigitsDiscard changesDisplay formatDisplayed date formatDisplayed valueDo you want to proceed?Documentation...DownloadE-Mail...E-mail %sEditEdit User PreferencesEdit selected recordEdit...Edited atEdited byEditionEdition ShortcutsEncoding:ErrorExpand all rowsExpand rowExpand/CollapseExport failedFalseFavoritesFetching databases listField nameFile to Import:FilesFindForeground ColorFormFormatFuzzyGlobalHeight:HelpHost / Database informationHost:IDID:IconsIgnore search limitImage SizeImage size too large.ImagesImport failedIncompatible version of the server.Insert copied rowsItalicJustifyKeyboard Shortcuts...Last Modified at:Last Modified by:Launch actionLimitLimit:Lines to Skip:LinkList EntriesList/Tree ShortcutsListed RecordsLoginLogs (%s)MManage...Mark line for deletion/removalMark line for removalModel:MonthMove CursorMove downMove down of one pageMove leftMove rightMove to bottomMove to parentMove to topMove upMove up of one pageNameNewNextNext RecordNext entryNext tabNoNo action defined.No other language available.No result found.Not Found.Not found.Note (%d/%d)Notes (%s)Notes...OKOpenOpen filtersOpen related recordsOpen relationOpen reportOpen the calendarOpen the record Open...Open/Search relationOptionsOverride '%s' definition?PDA ModePNG image (*.png)PastePaste copied textPlay Sound for Code ScannerPre-validationPreferencePreferencesPreferences...PreviewPreviousPrevious RecordPrevious entryPrevious tabPrintPrint reportPrint...ProfileProfile EditorProfile:QuitQuote char:Record saved.Records not removed.Records removed.RelateRelate...Relation EntriesReload/UndoRemove "%s"Remove Remove FileRemove selected profileRemove selected recordRemove this bookmarkReportReport BugReport...ReportsRevisionRevision:SaveSave AsSave As...Save Column WidthSave Tree StateSave and NewSave this recordSave your current versionScanSearchSearch %sSearch Limit SettingsSearch Limit...Search a record Search menuSee the modified versionSelectSelect FileSelect a colorSelect a revisionSelect allSelect parentSelect your actionSelect...Select/Activate current rowSelected RecordsSelectionSendSend an e-mail using the recordShortcutsShow active recordsShow bookmarks of filtersShow inactive recordsShow plain textShow revisions...Spell CheckingSubject:SwitchSwitch ViewSwitch viewTemplateTextText EntriesText and IconsThe following action requires to close all tabs. Do you want to continue?The number of decimalThe values of "%s" are not valid.This record has been modified do you want to save it?This record has been modified while you were editing it.To:TodayToggle menuToggle rowToggle selectionToo many requests. Try again later.ToolbarTranslate viewTranslationTrueUnable to check for new version.Unable to complete email entryUnable to set locale %sUnable to set view tree stateUndelete selected record UnderlineUnknownUnknown column header "%s"Unmark line for deletionUnselect allUse locale formatUser name:Username:ValueView Logs...View _Logs...WeekWhat is the name of this export?Width:WizardWorking now on the duplicated record(s).Write AnywayYYesYou have to select one record.You must select an import file first.You need to save the record before adding translations.Your selection:_Actions..._Add_Auto-Detect_Cancel_Clear_Close Tab_Copy URL_Delete Export_Delete..._Duplicate_E-Mail..._Export Data..._Import Data..._New_Next_Notes..._Previous_Print..._Relate..._Reload/Undo_Remove_Report..._Save_Save Export_Search_Switch View_URL Exportddevelopment modedisable thread usagego backgo forwardhlogging everything at INFO levelmmodularity, scalability and securitynext yearprevious yearsspecify alternate config filespecify the file used to output logging informationspecify the log level: DEBUG, INFO, WARNING, ERROR, CRITICALspecify the login userspecify the server hostname:portttranslator-creditswyProject-Id-Version: PROJECT VERSION Report-Msgid-Bugs-To: EMAIL@ADDRESS POT-Creation-Date: 2025-04-26 17:11+0200 PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE Last-Translator: FULL NAME Language: de Language-Team: de Plural-Forms: nplurals=2; plural=(n != 1); MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: 8bit Generated-By: Babel 2.17.0 "%s" liegt nicht im gültigen Wertebereich (Domain).In Feld "%s" ist ein Eintrag erforderlich.#FEHLER%d Datensatz importiert.%d Datensatz gespeichert.%d Datensätze importiert.%d Datensätze gespeichert.%s (%s)%s (Bezeichnung des Modells)%s (string)%s%%%s/Bezeichnung des Datensatzes, ,........:Alle FelderAusgewählte FelderVordefinierte ExporteErstelle...Suche...Eine neue Version ist verfügbar!A_nhänge...Über...AktionAktionen...HinzufügenEine Notiz zu einem Datensatz hinzufügenEinen Anhang zum Datensatz hinzufügenHinzufügen und NeuBestehenden Datensatz hinzufügenFeldnamen hinzufügenNeues Profil hinzufügenWert hinzufügenHinzufügen...Ausrichtung mittigAusrichtung linksAusrichtung rechtsAlle DateienDiese Warnung künftig nicht mehr anzeigen.AnwendungsfehlerTastenkombinationen AnwendungÄnderungen anwendenMöchten Sie diesen Datensatz wirklich löschen?Möchten Sie diese Datensätze wirklich löschen?Anhang (%s)AnhängeAnhänge (%s)Anhänge...Bcc:TextkörperFettLesezeichenname:Diesen Filter als Lesezeichen hinzufügenVon: CSV-Export: %sCSV-Import: %sCSV-Parameter_VerbindenAbbrechenAbbrechenSpeichern abbrechenCc:Überprüfung der URL: %sNach neuer Version suchenSprache auswählenLeerenFeld löschen SchließenRegisterkarte schließenCodeBarcode ScannerAlle Zeilen einklappenZeile einklappenVergleichenVergleichen: %sAktualisierungskonfliktVerbindung zum Tryton Server herstellenKopierenURL in Zwischenablage kopieren_URL kopieren...Ausgewählte Zeilen kopierenAusgewählten Text kopierenVerbindung zum Server nicht möglich.Die Session-Anforderung ist fehlgeschlagen.Erstelle "%s" …Neuen Datensatz erstellenNeuen Datensatz erstellen Neue Beziehung erstellenNeue Zeile erstellen/auswählenErstellt amErstellt am:Erstellt vonErstellt von:Ausgewählten Text ausschneidenDatenbank:DatumsformatTagStandardLöschenAusgewählten Datensatz löschenAusgewählte Datensätze löschen Feldtrenner:Lesen der Datei fehlgeschlagenNachkommastellenÄnderungen verwerfenAngezeigtes FormatAngezeigtes DatumsformatAngezeigter WertFortfahren?Dokumentation...HerunterladenE-Mail...E-mail %sBearbeitenBenutzereinstellungen bearbeitenAusgewählten Datensatz bearbeitenBearbeiten...Bearbeitet umBearbeitet vonBearbeitungTastenkombinationen BearbeitungZeichenkodierung:FehlerAlle Zeilen ausklappenZeile ausklappenAusklappen/EinklappenExport fehlgeschlagenFalschFavoritenDatenbankliste wird abgefragt...FeldnameImportdatei:DateienSuchenVordergrundfarbeFormularFormatFuzzyGlobalHöhe:HilfeHost-/DatenbankdetailsHost:IDID:IconsSuchlimit ignorierenBildgrößeBild ist zu groß.BilderImport fehlgeschlagenInkompatible Serverversion.Kopierte Zeilen einfügenKursivBlocksatzTastenkombinationen...Zuletzt verändert am:Zuletzt verändert von:Aktion ausführenLimitLimit:Zu überspringende Zeilen:VerknüpfungListenfelderTastenkombinationen Listen-/BaumansichtAngezeigte DatensätzeLog-inLogs (%s)MVerwalten...Zeile zum Löschen/Entfernen markierenZeile zum Entfernen markierenModell:MonatCursor bewegenNach unten bewegenEine Seite nach unten bewegenNach links bewegenNach rechts bewegenZum Ende bewegenZum übergeordneten Datensatz bewegenAn den Anfang bewegenNach oben bewegenEine Seite nach oben bewegenNameNeuNächsterNächster DatensatzNächstes FeldNächste RegisterkarteNeinKeine Aktion definiert.Keine weitere Sprache verfügbar.Kein Ergebnis.Nicht gefunden.Nicht gefunden.Notiz (%d/%d)Notizen (%s)Notizen...OKÖffnenFilter öffnenDatensätze einer Beziehung öffnenBeziehung öffnenBericht öffnenKalender öffnenDatensatz öffnen Öffnen...Beziehung suchen/öffnenEinstellungenDefinition von '%s' nicht berücksichtigen?Einspaltiger ModusPNG-Bild (*.png)EinfügenAusgewählten Text einfügenTon für Barcode Scanner wiedergebenVorvalidierungEinstellungEinstellungenEinstellungen...VorschauVorherigerVorheriger DatensatzVorheriges FeldVorherige RegisterkarteDruckenBericht druckenDrucken...ProfilProfilbearbeitungProfil:BeendenAnführungszeichen:Der Datensatz wurde gespeichert.Die Datensätze wurden nicht gelöscht.Die Datensätze wurden gelöscht.BeziehungBeziehungen...BeziehungsfelderNeu laden/Rückgängig"%s" entfernenEntfernen Datei entfernenAusgewähltes Profil löschenAusgewählten Datensatz löschen Dieses Lesezeichen entfernenBerichtFehler meldenBerichte...BerichteBearbeitungBearbeitung:SpeichernSpeichern unter…Speichern unter...Spaltenbreiten speichernMenüansicht speichernSpeichern und NeuDiesen Datensatz speichernIhre aktuelle Version speichernScannenSuchenSuche %sEinstellung SuchlimitSuchlimitierung...Datensatz suchen Menü durchsuchenDie geänderte Version ansehenAuswählenDatei auswählenEine Farbe auswählenBearbeitung auswählenAlle auswählenÜbergeordneten Datensatz auswählenAktion wählenAuswählen...Aktuelle Zeile auswählen/aktivierenAusgewählte DatensätzeAuswahlSendenEine E-Mail basierend auf diesem Datensatz versendenTastenkombinationenAktive Datensätze anzeigenFilterlesezeichen anzeigenInaktive Datensätze anzeigenKlartext anzeigenBearbeitungen anzeigen...RechtschreibkorrekturBetreff:Ansicht wechselnAnsicht wechselnAnsicht wechselnVorlageTextTextfelderText und IconsDie folgende Aktion erfordert die Schließung aller Registerkarten. Fortfahren?Anzahl DezimalstellenDie Werte von "%s" sind ungültig.Datensatz geändert. Soll der Datensatz gespeichert werden?Dieser Datensatz wurde anderweitig geändert, während Sie ihn bearbeitet haben.An:HeuteMenü ein-/ausblendenAuswahl für Zeile umkehrenAuswahl umkehrenZu viele Anfragen. Bitte versuchen Sie es später erneut.WerkzeugleisteAktuelle Sicht übersetzenÜbersetzungWahrEs war nicht möglich nach einer neuen Version zu suchen.Die automatische Vervollständigung des E-Mail Eintrags ist fehlgeschlagenKann locale %s nicht setzen.Kann Sicht für den Menübaum nicht anwenden.Löschung der ausgewählten Datensätze rückgängig machen UnterstrichenUnbekanntUnbekannte Spaltenüberschrift "%s"Löschmarkierung für Zeile aufhebenAuswahl aufhebenLokales Format verwendenAnmeldename:Anmeldename:WertLogs anzeigen..._Protokoll ansehen...WocheWie soll der Name des Exports lauten?Breite:AssistentSie arbeiten nun an dem/den duplizierten Datensatz/Datensätzen.ÜberschreibenJJaEin Datensatz muss ausgewählt werden.Es muss zuerst eine Importdatei ausgewählt werden.Der Datensatz muss gespeichert werden, bevor eine Übersetzung hinzugefügt werden kann.Ihre Auswahl:_Aktion ausführen..._Hinzufügen_Automatische Erkennung_Abbrechen_LeerenRegisterkarte schließenURL kopierenExportkonfiguration _löschen_Löschen..._Duplizieren_Bericht per E-Mail...Daten _exportieren...Daten _importieren..._Neu_Nächster_Notizen..._Vorheriger_Bericht drucken..._Beziehung öffnen...Neu laden/_Rückgängig_Entfernen_Bericht öffnen..._SpeichernExportkonfiguration _speichern_Suche_Ansicht wechseln_URL-ExporttEntwicklungsmodusThreading deaktivierenRückwärts bewegenVorwärts bewegenhAllgemeiner Log-Level: INFOmModularität, Skalierbarkeit und SicherheitNächstes JahrVorheriges JahrsAlternative Konfigurationsdatei angebenLogdatei angebenLog-Level angeben: DEBUG, INFO, WARNING, ERROR, CRITICALAnmeldename angebenDen Server mit Hostname:Port angeben.wMathias Behrle Clemens Hupka Korbinian Preisler Udo SpallekWJ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685599.0 tryton-7.0.24/tryton/data/locale/de/LC_MESSAGES/tryton.po0000644000175000017500000005140514517761237021060 0ustar00cedced# German (Germany) translations for tryton. # Copyright (C) 2008-2014 MBSolutions # This file is distributed under the same license as the tryton project. # Udo Spallek , 2008. # Mathias Behrle , 2008-2014. msgid "" msgstr "Content-Type: text/plain; charset=utf-8\n" msgid "specify alternate config file" msgstr "Alternative Konfigurationsdatei angeben" msgid "development mode" msgstr "Entwicklungsmodus" msgid "logging everything at INFO level" msgstr "Allgemeiner Log-Level: INFO" msgid "specify the log level: DEBUG, INFO, WARNING, ERROR, CRITICAL" msgstr "Log-Level angeben: DEBUG, INFO, WARNING, ERROR, CRITICAL" msgid "specify the file used to output logging information" msgstr "Logdatei angeben" msgid "specify the login user" msgstr "Anmeldename angeben" msgid "specify the server hostname:port" msgstr "Den Server mit Hostname:Port angeben." msgid "disable thread usage" msgstr "Threading deaktivieren" #, python-format msgid "Unable to set locale %s" msgstr "Kann locale %s nicht setzen." msgid ", " msgstr ", " msgid ",..." msgstr ",..." #, python-format msgid "%s (%s)" msgstr "%s (%s)" msgid "Select your action" msgstr "Aktion wählen" msgid "No action defined." msgstr "Keine Aktion definiert." msgid "By: " msgstr "Von: " msgid "Selection" msgstr "Auswahl" msgid "Cancel" msgstr "Abbrechen" msgid "OK" msgstr "OK" msgid "Your selection:" msgstr "Ihre Auswahl:" msgid "Save As..." msgstr "Speichern unter..." msgid "Do you want to proceed?" msgstr "Fortfahren?" msgid "Always ignore this warning." msgstr "Diese Warnung künftig nicht mehr anzeigen." msgid "No" msgstr "Nein" msgid "Yes" msgstr "Ja" msgid "Concurrency Exception" msgstr "Aktualisierungskonflikt" msgid "This record has been modified while you were editing it." msgstr "" "Dieser Datensatz wurde anderweitig geändert, während Sie ihn bearbeitet " "haben." msgid "Cancel saving" msgstr "Speichern abbrechen" msgid "Compare" msgstr "Vergleichen" msgid "See the modified version" msgstr "Die geänderte Version ansehen" msgid "Write Anyway" msgstr "Überschreiben" msgid "Save your current version" msgstr "Ihre aktuelle Version speichern" #, python-format msgid "Compare: %s" msgstr "Vergleichen: %s" msgid "Close" msgstr "Schließen" msgid "Application Error" msgstr "Anwendungsfehler" msgid "Report Bug" msgstr "Fehler melden" #, python-format msgid "Check URL: %s" msgstr "Überprüfung der URL: %s" msgid "Unable to check for new version." msgstr "Es war nicht möglich nach einer neuen Version zu suchen." msgid "A new version is available!" msgstr "Eine neue Version ist verfügbar!" msgid "Download" msgstr "Herunterladen" msgid "Could not get a session." msgstr "Die Session-Anforderung ist fehlgeschlagen." msgid "Too many requests. Try again later." msgstr "Zu viele Anfragen. Bitte versuchen Sie es später erneut." msgid "Not found." msgstr "Nicht gefunden." msgid "Not Found." msgstr "Nicht gefunden." msgid "..." msgstr "..." msgid "Search..." msgstr "Suche..." msgid "Create..." msgstr "Erstelle..." #, python-format msgid "Create \"%s\"..." msgstr "Erstelle \"%s\" …" msgid "Value" msgstr "Wert" msgid "Displayed value" msgstr "Angezeigter Wert" msgid "Format" msgstr "Format" msgid "Display format" msgstr "Angezeigtes Format" msgid "Open the calendar" msgstr "Kalender öffnen" msgid "Date Format" msgstr "Datumsformat" msgid "Displayed date format" msgstr "Angezeigtes Datumsformat" msgid "y" msgstr "J" msgid "True" msgstr "Wahr" msgid "t" msgstr "w" msgid "False" msgstr "Falsch" msgid "Digits" msgstr "Nachkommastellen" msgid "The number of decimal" msgstr "Anzahl Dezimalstellen" msgid "Template" msgstr "Vorlage" msgid "Edit..." msgstr "Bearbeiten..." msgid "View Logs..." msgstr "Logs anzeigen..." msgid "Attachments..." msgstr "Anhänge..." msgid "Notes..." msgstr "Notizen..." msgid "Actions..." msgstr "Aktionen..." msgid "Relate..." msgstr "Beziehungen..." msgid "Report..." msgstr "Berichte..." msgid "Print..." msgstr "Drucken..." msgid "E-Mail..." msgstr "E-Mail..." msgid "Bold" msgstr "Fett" msgid "Italic" msgstr "Kursiv" msgid "Underline" msgstr "Unterstrichen" msgid "Align Left" msgstr "Ausrichtung links" msgid "Align Center" msgstr "Ausrichtung mittig" msgid "Align Right" msgstr "Ausrichtung rechts" msgid "Justify" msgstr "Blocksatz" msgid "Foreground Color" msgstr "Vordergrundfarbe" msgid "Select a color" msgstr "Eine Farbe auswählen" msgid "Y" msgstr "J" msgid "M" msgstr "M" msgid "w" msgstr "W" msgid "d" msgstr "t" msgid "h" msgstr "h" msgid "m" msgstr "m" msgid "s" msgstr "s" msgid "Preferences..." msgstr "Einstellungen..." msgid "Toolbar" msgstr "Werkzeugleiste" msgid "Default" msgstr "Standard" msgid "Text and Icons" msgstr "Text und Icons" msgid "Text" msgstr "Text" msgid "Icons" msgstr "Icons" msgid "Form" msgstr "Formular" msgid "Save Column Width" msgstr "Spaltenbreiten speichern" msgid "Save Tree State" msgstr "Menüansicht speichern" msgid "Spell Checking" msgstr "Rechtschreibkorrektur" msgid "Play Sound for Code Scanner" msgstr "Ton für Barcode Scanner wiedergeben" msgid "PDA Mode" msgstr "Einspaltiger Modus" msgid "Search Limit..." msgstr "Suchlimitierung..." msgid "Check Version" msgstr "Nach neuer Version suchen" msgid "Options" msgstr "Einstellungen" msgid "Documentation..." msgstr "Dokumentation..." msgid "Keyboard Shortcuts..." msgstr "Tastenkombinationen..." msgid "About..." msgstr "Über..." msgid "Help" msgstr "Hilfe" msgid "No result found." msgstr "Kein Ergebnis." msgid "Favorites" msgstr "Favoriten" msgid "Manage..." msgstr "Verwalten..." msgid "Action" msgstr "Aktion" msgid "" "The following action requires to close all tabs.\n" "Do you want to continue?" msgstr "" "Die folgende Aktion erfordert die Schließung aller Registerkarten.\n" "Fortfahren?" msgid "Application Shortcuts" msgstr "Tastenkombinationen Anwendung" msgid "Global" msgstr "Global" msgid "Preferences" msgstr "Einstellungen" msgid "Search menu" msgstr "Menü durchsuchen" msgid "Toggle menu" msgstr "Menü ein-/ausblenden" msgid "Previous tab" msgstr "Vorherige Registerkarte" msgid "Next tab" msgstr "Nächste Registerkarte" msgid "Shortcuts" msgstr "Tastenkombinationen" msgid "Quit" msgstr "Beenden" msgid "Edition Shortcuts" msgstr "Tastenkombinationen Bearbeitung" msgid "Text Entries" msgstr "Textfelder" msgid "Cut selected text" msgstr "Ausgewählten Text ausschneiden" msgid "Copy selected text" msgstr "Ausgewählten Text kopieren" msgid "Paste copied text" msgstr "Ausgewählten Text einfügen" msgid "Next entry" msgstr "Nächstes Feld" msgid "Previous entry" msgstr "Vorheriges Feld" msgid "Relation Entries" msgstr "Beziehungsfelder" msgid "Create new relation" msgstr "Neue Beziehung erstellen" msgid "Open/Search relation" msgstr "Beziehung suchen/öffnen" msgid "List Entries" msgstr "Listenfelder" msgid "Switch view" msgstr "Ansicht wechseln" msgid "Create/Select new line" msgstr "Neue Zeile erstellen/auswählen" msgid "Open relation" msgstr "Beziehung öffnen" msgid "Mark line for deletion/removal" msgstr "Zeile zum Löschen/Entfernen markieren" msgid "Mark line for removal" msgstr "Zeile zum Entfernen markieren" msgid "Unmark line for deletion" msgstr "Löschmarkierung für Zeile aufheben" msgid "List/Tree Shortcuts" msgstr "Tastenkombinationen Listen-/Baumansicht" msgid "Move Cursor" msgstr "Cursor bewegen" msgid "Move right" msgstr "Nach rechts bewegen" msgid "Move left" msgstr "Nach links bewegen" msgid "Move up" msgstr "Nach oben bewegen" msgid "Move down" msgstr "Nach unten bewegen" msgid "Move up of one page" msgstr "Eine Seite nach oben bewegen" msgid "Move down of one page" msgstr "Eine Seite nach unten bewegen" msgid "Move to top" msgstr "An den Anfang bewegen" msgid "Move to bottom" msgstr "Zum Ende bewegen" msgid "Move to parent" msgstr "Zum übergeordneten Datensatz bewegen" msgid "Edition" msgstr "Bearbeitung" msgid "Copy selected rows" msgstr "Ausgewählte Zeilen kopieren" msgid "Insert copied rows" msgstr "Kopierte Zeilen einfügen" msgid "Select all" msgstr "Alle auswählen" msgid "Unselect all" msgstr "Auswahl aufheben" msgid "Select parent" msgstr "Übergeordneten Datensatz auswählen" msgid "Select/Activate current row" msgstr "Aktuelle Zeile auswählen/aktivieren" msgid "Toggle selection" msgstr "Auswahl umkehren" msgid "Expand/Collapse" msgstr "Ausklappen/Einklappen" msgid "Expand row" msgstr "Zeile ausklappen" msgid "Collapse row" msgstr "Zeile einklappen" msgid "Toggle row" msgstr "Auswahl für Zeile umkehren" msgid "Collapse all rows" msgstr "Alle Zeilen einklappen" msgid "Expand all rows" msgstr "Alle Zeilen ausklappen" msgid "Close Tab" msgstr "Registerkarte schließen" msgid "modularity, scalability and security" msgstr "Modularität, Skalierbarkeit und Sicherheit" msgid "translator-credits" msgstr "" "Mathias Behrle\n" "Clemens Hupka\n" "Korbinian Preisler\n" "Udo Spallek" #, python-format msgid "Attachments (%s)" msgstr "Anhänge (%s)" msgid "Code Scanner" msgstr "Barcode Scanner" msgid "Code" msgstr "Code" msgid "Profile Editor" msgstr "Profilbearbeitung" msgid "Profile" msgstr "Profil" msgid "Add new profile" msgstr "Neues Profil hinzufügen" msgid "Remove selected profile" msgstr "Ausgewähltes Profil löschen" msgid "Host:" msgstr "Host:" msgid "Database:" msgstr "Datenbank:" msgid "Fetching databases list" msgstr "Datenbankliste wird abgefragt..." msgid "Username:" msgstr "Anmeldename:" msgid "Incompatible version of the server." msgstr "Inkompatible Serverversion." msgid "Could not connect to the server." msgstr "Verbindung zum Server nicht möglich." msgid "Login" msgstr "Log-in" msgid "_Cancel" msgstr "_Abbrechen" msgid "Cancel connection to the Tryton server" msgstr "Abbrechen" msgid "C_onnect" msgstr "_Verbinden" msgid "Connect the Tryton server" msgstr "Verbindung zum Tryton Server herstellen" msgid "Profile:" msgstr "Profil:" msgid "Host / Database information" msgstr "Host-/Datenbankdetails" msgid "User name:" msgstr "Anmeldename:" msgid "Unable to complete email entry" msgstr "" "Die automatische Vervollständigung des E-Mail Eintrags ist fehlgeschlagen" #, python-format msgid "E-mail %s" msgstr "E-mail %s" msgid "To:" msgstr "An:" msgid "Cc:" msgstr "Cc:" msgid "Bcc:" msgstr "Bcc:" msgid "Subject:" msgstr "Betreff:" msgid "Body" msgstr "Textkörper" msgid "Reports" msgstr "Berichte" msgid "Attachments" msgstr "Anhänge" msgid "Files" msgstr "Dateien" msgid "Send" msgstr "Senden" msgid "Select File" msgstr "Datei auswählen" msgid "Remove File" msgstr "Datei entfernen" msgid "Select" msgstr "Auswählen" msgid "Add..." msgstr "Hinzufügen..." msgid "Preview" msgstr "Vorschau" msgid "Previous" msgstr "Vorheriger" msgid "Next" msgstr "Nächster" #, python-format msgid "Attachment (%s)" msgstr "Anhang (%s)" #, python-format msgid "Note (%d/%d)" msgstr "Notiz (%d/%d)" msgid "You have to select one record." msgstr "Ein Datensatz muss ausgewählt werden." msgid "Are you sure to remove this record?" msgstr "Möchten Sie diesen Datensatz wirklich löschen?" msgid "Are you sure to remove those records?" msgstr "Möchten Sie diese Datensätze wirklich löschen?" msgid "Records not removed." msgstr "Die Datensätze wurden nicht gelöscht." msgid "Records removed." msgstr "Die Datensätze wurden gelöscht." msgid "Working now on the duplicated record(s)." msgstr "Sie arbeiten nun an dem/den duplizierten Datensatz/Datensätzen." msgid "Record saved." msgstr "Der Datensatz wurde gespeichert." msgid "" "This record has been modified\n" "do you want to save it?" msgstr "" "Datensatz geändert.\n" "Soll der Datensatz gespeichert werden?" msgid "Launch action" msgstr "Aktion ausführen" msgid "Relate" msgstr "Beziehung" msgid "Open related records" msgstr "Datensätze einer Beziehung öffnen" msgid "Report" msgstr "Bericht" msgid "Open report" msgstr "Bericht öffnen" msgid "Print" msgstr "Drucken" msgid "Print report" msgstr "Bericht drucken" msgid "_Copy URL" msgstr "URL kopieren" msgid "Copy URL into clipboard" msgstr "URL in Zwischenablage kopieren" msgid "Unknown" msgstr "Unbekannt" msgid "Limit" msgstr "Limit" msgid "Search Limit Settings" msgstr "Einstellung Suchlimit" msgid "Limit:" msgstr "Limit:" #, python-format msgid "Logs (%s)" msgstr "Logs (%s)" msgid "Model:" msgstr "Modell:" msgid "ID:" msgstr "ID:" msgid "Created by:" msgstr "Erstellt von:" msgid "Created at:" msgstr "Erstellt am:" msgid "Last Modified by:" msgstr "Zuletzt verändert von:" msgid "Last Modified at:" msgstr "Zuletzt verändert am:" #, python-format msgid "Notes (%s)" msgstr "Notizen (%s)" msgid "Edit User Preferences" msgstr "Benutzereinstellungen bearbeiten" msgid "Preference" msgstr "Einstellung" msgid "Revision" msgstr "Bearbeitung" msgid "Select a revision" msgstr "Bearbeitung auswählen" msgid "Revision:" msgstr "Bearbeitung:" msgid "_Switch View" msgstr "_Ansicht wechseln" msgid "Switch View" msgstr "Ansicht wechseln" msgid "_Previous" msgstr "_Vorheriger" msgid "Previous Record" msgstr "Vorheriger Datensatz" msgid "_Next" msgstr "_Nächster" msgid "Next Record" msgstr "Nächster Datensatz" msgid "_Search" msgstr "_Suche" msgid "_New" msgstr "_Neu" msgid "Create a new record" msgstr "Neuen Datensatz erstellen" msgid "_Save" msgstr "_Speichern" msgid "Save this record" msgstr "Diesen Datensatz speichern" msgid "_Reload/Undo" msgstr "Neu laden/_Rückgängig" msgid "Reload/Undo" msgstr "Neu laden/Rückgängig" msgid "_Duplicate" msgstr "_Duplizieren" msgid "_Delete..." msgstr "_Löschen..." msgid "View _Logs..." msgstr "_Protokoll ansehen..." msgid "Show revisions..." msgstr "Bearbeitungen anzeigen..." msgid "A_ttachments..." msgstr "A_nhänge..." msgid "Add an attachment to the record" msgstr "Einen Anhang zum Datensatz hinzufügen" msgid "_Notes..." msgstr "_Notizen..." msgid "Add a note to the record" msgstr "Eine Notiz zu einem Datensatz hinzufügen" msgid "_Actions..." msgstr "_Aktion ausführen..." msgid "_Relate..." msgstr "_Beziehung öffnen..." msgid "_Report..." msgstr "_Bericht öffnen..." msgid "_Print..." msgstr "_Bericht drucken..." msgid "_E-Mail..." msgstr "_Bericht per E-Mail..." msgid "Send an e-mail using the record" msgstr "Eine E-Mail basierend auf diesem Datensatz versenden" msgid "_Export Data..." msgstr "Daten _exportieren..." msgid "_Import Data..." msgstr "Daten _importieren..." msgid "Copy _URL..." msgstr "_URL kopieren..." msgid "_Close Tab" msgstr "Registerkarte schließen" msgid "All fields" msgstr "Alle Felder" msgid "_Add" msgstr "_Hinzufügen" msgid "_Remove" msgstr "_Entfernen" msgid "_Clear" msgstr "_Leeren" msgid "Fields selected" msgstr "Ausgewählte Felder" msgid "CSV Parameters" msgstr "CSV-Parameter" msgid "Delimiter:" msgstr "Feldtrenner:" msgid "Quote char:" msgstr "Anführungszeichen:" msgid "Encoding:" msgstr "Zeichenkodierung:" msgid "Use locale format" msgstr "Lokales Format verwenden" msgid "Field name" msgstr "Feldname" #, python-format msgid "CSV Export: %s" msgstr "CSV-Export: %s" msgid "_Save Export" msgstr "Exportkonfiguration _speichern" msgid "_URL Export" msgstr "_URL-Export" msgid "_Delete Export" msgstr "Exportkonfiguration _löschen" msgid "Predefined exports" msgstr "Vordefinierte Exporte" msgid "Name" msgstr "Name" msgid "Open" msgstr "Öffnen" msgid "Save" msgstr "Speichern" msgid "Listed Records" msgstr "Angezeigte Datensätze" msgid "Selected Records" msgstr "Ausgewählte Datensätze" msgid "Ignore search limit" msgstr "Suchlimit ignorieren" msgid "Add field names" msgstr "Feldnamen hinzufügen" #, python-format msgid "%s (string)" msgstr "%s (string)" #, python-format msgid "%s (model name)" msgstr "%s (Bezeichnung des Modells)" #, python-format msgid "%s/Record Name" msgstr "%s/Bezeichnung des Datensatzes" msgid "What is the name of this export?" msgstr "Wie soll der Name des Exports lauten?" #, python-format msgid "Override '%s' definition?" msgstr "Definition von '%s' nicht berücksichtigen?" #, python-format msgid "%d record saved." msgstr "%d Datensatz gespeichert." #, python-format msgid "%d records saved." msgstr "%d Datensätze gespeichert." msgid "Export failed" msgstr "Export fehlgeschlagen" msgid "Link" msgstr "Verknüpfung" msgid "Delete" msgstr "Löschen" msgid "Discard changes" msgstr "Änderungen verwerfen" msgid "Save and New" msgstr "Speichern und Neu" msgid "Add and New" msgstr "Hinzufügen und Neu" msgid "Add" msgstr "Hinzufügen" msgid "Apply changes" msgstr "Änderungen anwenden" msgid "Switch" msgstr "Ansicht wechseln" msgid "Remove " msgstr "Entfernen " msgid "Create a new record " msgstr "Neuen Datensatz erstellen " msgid "Delete selected record " msgstr "Ausgewählte Datensätze löschen " msgid "Undelete selected record " msgstr "Löschung der ausgewählten Datensätze rückgängig machen " #, python-format msgid "CSV Import: %s" msgstr "CSV-Import: %s" msgid "_Auto-Detect" msgstr "_Automatische Erkennung" msgid "File to Import:" msgstr "Importdatei:" msgid "Open..." msgstr "Öffnen..." msgid "Lines to Skip:" msgstr "Zu überspringende Zeilen:" msgid "You must select an import file first." msgstr "Es muss zuerst eine Importdatei ausgewählt werden." msgid "Detection failed" msgstr "Lesen der Datei fehlgeschlagen" #, python-format msgid "Unknown column header \"%s\"" msgstr "Unbekannte Spaltenüberschrift \"%s\"" msgid "Error" msgstr "Fehler" msgid "Import failed" msgstr "Import fehlgeschlagen" #, python-format msgid "%d record imported." msgstr "%d Datensatz importiert." #, python-format msgid "%d records imported." msgstr "%d Datensätze importiert." msgid "Search" msgstr "Suchen" msgid "New" msgstr "Neu" #, python-format msgid "Search %s" msgstr "Suche %s" msgid "Wizard" msgstr "Assistent" msgid "ID" msgstr "ID" msgid "Created by" msgstr "Erstellt von" msgid "Created at" msgstr "Erstellt am" msgid "Edited by" msgstr "Bearbeitet von" msgid "Edited at" msgstr "Bearbeitet um" msgid "Unable to set view tree state" msgstr "Kann Sicht für den Menübaum nicht anwenden." #, python-format msgid "\"%s\" is not valid according to its domain." msgstr "\"%s\" liegt nicht im gültigen Wertebereich (Domain)." #, python-format msgid "\"%s\" is required." msgstr "In Feld \"%s\" ist ein Eintrag erforderlich." #, python-format msgid "The values of \"%s\" are not valid." msgstr "Die Werte von \"%s\" sind ungültig." msgid "Pre-validation" msgstr "Vorvalidierung" msgid ":" msgstr ":" msgid "Scan" msgstr "Scannen" msgid "Image Size" msgstr "Bildgröße" msgid "Width:" msgstr "Breite:" msgid "Height:" msgstr "Höhe:" msgid "PNG image (*.png)" msgstr "PNG-Bild (*.png)" msgid "Save As" msgstr "Speichern unter…" msgid "Image size too large." msgstr "Bild ist zu groß." msgid "Copy" msgstr "Kopieren" msgid "Paste" msgstr "Einfügen" msgid ".." msgstr ".." msgid "Open filters" msgstr "Filter öffnen" msgid "Show bookmarks of filters" msgstr "Filterlesezeichen anzeigen" msgid "Remove this bookmark" msgstr "Dieses Lesezeichen entfernen" msgid "Bookmark this filter" msgstr "Diesen Filter als Lesezeichen hinzufügen" msgid "Show active records" msgstr "Aktive Datensätze anzeigen" msgid "Show inactive records" msgstr "Inaktive Datensätze anzeigen" msgid "Bookmark Name:" msgstr "Lesezeichenname:" msgid "Find" msgstr "Suchen" msgid "Today" msgstr "Heute" msgid "go back" msgstr "Rückwärts bewegen" msgid "go forward" msgstr "Vorwärts bewegen" msgid "previous year" msgstr "Vorheriges Jahr" msgid "next year" msgstr "Nächstes Jahr" msgid "Day" msgstr "Tag" msgid "Week" msgstr "Woche" msgid "Month" msgstr "Monat" msgid "Select..." msgstr "Auswählen..." msgid "Clear" msgstr "Leeren" msgid "All files" msgstr "Alle Dateien" msgid "Show plain text" msgstr "Klartext anzeigen" msgid "Add value" msgstr "Wert hinzufügen" #, python-format msgid "Remove \"%s\"" msgstr "\"%s\" entfernen" msgid "Images" msgstr "Bilder" msgid "Add existing record" msgstr "Bestehenden Datensatz hinzufügen" msgid "Remove selected record" msgstr "Ausgewählten Datensatz löschen " msgid "Open the record " msgstr "Datensatz öffnen " msgid "Clear the field " msgstr "Feld löschen " msgid "Search a record " msgstr "Datensatz suchen " msgid "Edit selected record" msgstr "Ausgewählten Datensatz bearbeiten" msgid "Delete selected record" msgstr "Ausgewählten Datensatz löschen" #, python-format msgid "%s%%" msgstr "%s%%" msgid "Choose a language" msgstr "Sprache auswählen" msgid "Translation" msgstr "Übersetzung" msgid "Edit" msgstr "Bearbeiten" msgid "Fuzzy" msgstr "Fuzzy" msgid "You need to save the record before adding translations." msgstr "" "Der Datensatz muss gespeichert werden, bevor eine Übersetzung hinzugefügt " "werden kann." msgid "No other language available." msgstr "Keine weitere Sprache verfügbar." msgid "#ERROR" msgstr "#FEHLER" msgid "Translate view" msgstr "Aktuelle Sicht übersetzen" ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1745680285.1241102 tryton-7.0.24/tryton/data/locale/es/0000755000175000017500000000000015003173635015374 5ustar00cedced././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1745680285.1641107 tryton-7.0.24/tryton/data/locale/es/LC_MESSAGES/0000755000175000017500000000000015003173635017161 5ustar00cedced././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1745680279.0 tryton-7.0.24/tryton/data/locale/es/LC_MESSAGES/tryton.mo0000644000175000017500000004652415003173627021071 0ustar00cedcedL |*}  %16EHMPTVh  ?K_o      #%4Z jv&  2@ D R`rx     0=P c    &2 D NZ^fm  !2 ; EOTj        & 1 A G L ] b i o v ~         #  !3!:!B!X!j! |!!!!! !!!! !! !!","3" 9" E"O" e" o"z"" """""" " """"#!# 2# =# H# U#`#i#l# q#~# # #######$$.$4$F$b$ q$ |$$$$$$ $$ $$$$%% % !%/%D%U% \%f% w% % % %%%%% % %&& &#&(& 0&;&M& ]&j&{&&& &&&& && ' ''-' ?' J'X' k'u'' ''' ''' ((/(A(P(Y( `( l(x(( ((I((!)5$)8Z))) ) ))#))) * * *2*Q*i** **** ** + + + &+ 3+A+ F+g+n+(u+ ++++%+7+., >,J, O,\,d, k, v,, , , ,,,,, , , , , -- -$- *-7- ?- L-X-Z-k-- -- --$- - ---3.<I.. ......%g00000011'1 >1J1O1f1i1n1q1u1w11$111#1 2 "2/2 82D2M2j222222 23303E3!X3z3331333 4,4 54 C4O4T4\4 d444444 44)4"595=5V5i5|5555555 55 66,6L6S6q666)6$6 77*7G7]7 |7 7 7 777 77 777%8 >8I8b8k8}88888 8889969 S9 ]9 h9t9{9999 9999 9#:+:<:P:Y:`: o:z:::::): ::::::&; 9;C;9^;;; ;;;;<$<,<5<G<N<`<w<<<< <#<<<< < ==&=;=N=]=l= ==== ====="=> 9>Z>o> > >>>> >>> >>?$?-?D?$M?r?{???*?? ? ?@ @@(@:@K@]@f@ w@@@@@@@"@@ AA(A>A PA^AnAAA$AAA A B B B'B /BHdHHHHHHHHH*H&I /I:9ItIIII*I7IJ J*J3J BJLJUJfJuJ J JJJJJ J J J KK$K 7K AKNKWKmKuKKKKK K KK,K L& L 2L @LNL/PL>L=LL$M4M6MPMRM"%s" is not valid according to its domain."%s" is required.#ERROR%d record imported.%d record saved.%d records imported.%d records saved.%s (%s)%s (model name)%s (string)%s%%%s/Record Name, ,........:All fieldsFields selectedPredefined exportsCreate...Search...A new version is available!A_ttachments...About...ActionActions...AddAdd a note to the recordAdd an attachment to the recordAdd and NewAdd existing recordAdd field namesAdd new profileAdd valueAdd...Align CenterAlign LeftAlign RightAll filesAlways ignore this warning.Application ErrorApplication ShortcutsApply changesAre you sure to remove this record?Are you sure to remove those records?Attachment (%s)AttachmentsAttachments (%s)Attachments...Bcc:BodyBoldBookmark Name:Bookmark this filterBy: CSV Export: %sCSV Import: %sCSV ParametersC_onnectCancelCancel connection to the Tryton serverCancel savingCc:Check URL: %sCheck VersionChoose a languageClearClear the field CloseClose TabCodeCode ScannerCollapse all rowsCollapse rowCompareCompare: %sConcurrency ExceptionConnect the Tryton serverCopyCopy URL into clipboardCopy _URL...Copy selected rowsCopy selected textCould not connect to the server.Could not get a session.Create "%s"...Create a new recordCreate a new record Create new relationCreate/Select new lineCreated atCreated at:Created byCreated by:Cut selected textDatabase:Date FormatDayDefaultDeleteDelete selected recordDelete selected record Delimiter:Detection failedDigitsDiscard changesDisplay formatDisplayed date formatDisplayed valueDo you want to proceed?Documentation...DownloadE-Mail...E-mail %sEditEdit User PreferencesEdit selected recordEdit...Edited atEdited byEditionEdition ShortcutsEncoding:ErrorExpand all rowsExpand rowExpand/CollapseExport failedFalseFavoritesFetching databases listField nameFile to Import:FilesFindForeground ColorFormFormatFuzzyGlobalHeight:HelpHost / Database informationHost:IDID:IconsIgnore search limitImage SizeImage size too large.ImagesImport failedIncompatible version of the server.Insert copied rowsItalicJustifyKeyboard Shortcuts...Last Modified at:Last Modified by:Launch actionLimitLimit:Lines to Skip:LinkList EntriesList/Tree ShortcutsListed RecordsLoginLogs (%s)MManage...Mark line for deletion/removalMark line for removalModel:MonthMove CursorMove downMove down of one pageMove leftMove rightMove to bottomMove to parentMove to topMove upMove up of one pageNameNewNextNext RecordNext entryNext tabNoNo action defined.No other language available.No result found.Not Found.Not found.Note (%d/%d)Notes (%s)Notes...OKOpenOpen filtersOpen related recordsOpen relationOpen reportOpen the calendarOpen the record Open...Open/Search relationOptionsOverride '%s' definition?PDA ModePNG image (*.png)PastePaste copied textPlay Sound for Code ScannerPre-validationPreferencePreferencesPreferences...PreviewPreviousPrevious RecordPrevious entryPrevious tabPrintPrint reportPrint...ProfileProfile EditorProfile:QuitQuote char:Record saved.Records not removed.Records removed.RelateRelate...Relation EntriesReload/UndoRemove "%s"Remove Remove FileRemove selected profileRemove selected recordRemove this bookmarkReportReport BugReport...ReportsRevisionRevision:SaveSave AsSave As...Save Column WidthSave Tree StateSave and NewSave this recordSave your current versionScanSearchSearch %sSearch Limit SettingsSearch Limit...Search a record Search menuSee the modified versionSelectSelect FileSelect a colorSelect a revisionSelect allSelect parentSelect your actionSelect...Select/Activate current rowSelected RecordsSelectionSendSend an e-mail using the recordShortcutsShow active recordsShow bookmarks of filtersShow inactive recordsShow plain textShow revisions...Spell CheckingSubject:SwitchSwitch ViewSwitch viewTemplateTextText EntriesText and IconsThe following action requires to close all tabs. Do you want to continue?The number of decimalThe values of "%s" are not valid.This record has been modified do you want to save it?This record has been modified while you were editing it.To:TodayToggle menuToggle rowToggle selectionToo many requests. Try again later.ToolbarTranslate viewTranslationTrueUnable to check for new version.Unable to complete email entryUnable to set locale %sUnable to set view tree stateUndelete selected record UnderlineUnknownUnknown column header "%s"Unmark line for deletionUnselect allUse locale formatUser name:Username:ValueView Logs...View _Logs...WeekWhat is the name of this export?Width:WizardWorking now on the duplicated record(s).Write AnywayYYesYou have to select one record.You must select an import file first.You need to save the record before adding translations.Your selection:_Actions..._Add_Auto-Detect_Cancel_Clear_Close Tab_Copy URL_Delete Export_Delete..._Duplicate_E-Mail..._Export Data..._Import Data..._New_Next_Notes..._Previous_Print..._Relate..._Reload/Undo_Remove_Report..._Save_Save Export_Search_Switch View_URL Exportddevelopment modedisable thread usagego backgo forwardhlogging everything at INFO levelmmodularity, scalability and securitynext yearprevious yearsspecify alternate config filespecify the file used to output logging informationspecify the log level: DEBUG, INFO, WARNING, ERROR, CRITICALspecify the login userspecify the server hostname:portttranslator-creditswyProject-Id-Version: PROJECT VERSION Report-Msgid-Bugs-To: EMAIL@ADDRESS POT-Creation-Date: 2025-04-26 17:11+0200 PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE Last-Translator: FULL NAME Language: es Language-Team: es Plural-Forms: nplurals=2; plural=(n != 1); MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: 8bit Generated-By: Babel 2.17.0 "%s" no es válido según su dominio."%s" es obligatorio.#ERRORSe ha importado %d registro.Se ha guardado %d registro.Se han importado %d registros.Se han guardado %d registros.%s (%s)%s (nombre del modelo)%s (cadena)%s%%%s/Nombre del registro, ,........:Todos los camposCampos seleccionadosExportaciones predeterminadasCrear...Buscar...Esta disponible una nueva versión!Adjun_tos...Acerca de...AcciónAcciones...AñadirAñadir una nota al registroAñadir un adjunto al registroAñadir y nuevoAñadir un registro existenteAñadir nombres de campoAñadir un nuevo perfilAñadir un valorAñadir...Alinear en el centroAlinear a la izquierdaAlinear a la derechaTodos los archivosIgnorar siempre esta advertencia.Error de aplicaciónAtajos de la aplicaciónAplicar cambios¿Está seguro que quiere eliminar este registro?¿Está seguro que quiere eliminar estos registros?Adjuntos (%s)AdjuntosAdjuntos (%s)Adjuntos...CCo:MensajeNegritaNombre de la búsqueda favorita:Guardar como búsqueda favoritaPor: Exportación CSV: %sImportación CSV: %sParámetros CSVC_onectarCancelarCancelar conexión con el servidor TrytonNo guardes los cambiosCc:URL de comprobación: %sComprobar versiónEscoja un lenguajeLimpiarEliminar el registro CerrarCerrar pestañaCódigoEscáner de códigosContraer todas las filasContraer filaCompararComparar: %sExcepción de concurrenciaConectar con el servidor TrytonCopiarCopiar la URL al portapapelesCopiar _URL...Copiar filas seleccionadasCopiar texto seleccionadoNo se ha podido conectar con el servidor.No se ha podido obtener una sesión.Crear "%s"...Crear un nuevo registroCrear un nuevo registro Crear nueva relaciónCrear/Seleccionar línea nuevaCreado elCreado el:Creado porCreado por:Cortar texto seleccionadoBase de datos:Formato fechaDíaPor defectoEliminarEliminar registro seleccionadoEliminar registro seleccionado Separador:Ha fallado la detecciónDígitosDescartar cambiosFormat a mostrarFormato fecha actualValor a mostrar¿Desea continuar?Documentación...DescargarCorreo electrónico...Correo electrónico %sEditarEditar preferencias de usuarioEditar registro seleccionadoEditar...Editado elEditado porEditarAtajos de ediciónCodificación:ErrorExpandir todas las filasExpandir filaExpandir/ContraerHa fallado la exportaciónFalsoFavoritosRecuperando lista de bases de datosNombre del campoArchivo a importar:ArchivosBuscarColor de fondoFormularioFormatoDudosoGlobalAltura:AyudaInformación del Servidor / Base de datosServidor:IDID:IconosIgnorar límite de búsquedaTamaño de la imagenEl tamaño de la imagen es muy grande.ImágenesHa fallado la importaciónEl cliente no es compatible con la versión del servidor.Insertar filas copiadasCursivaJustificarCombinaciones de teclas...Última modificación el:Última modificación por:Ejecutar acciónLímiteLímite:Líneas a omitir:EnlaceLista de entradasAtajos de lista/árbolRegistros listadosAccederRegistros (%s)MGestionar...Marcar linea para eliminar/suprimirMarcar para eliminarModelo:MesMover cursorMover abajoMover abajo una páginaMover a la izquierdaMover a la derechaMover al finalMover al padreMover al principioMover arribaMover arriba una páginaNombreNuevoSiguienteRegistro siguienteSiguiente entradaPestaña siguienteNoNo se ha definido ninguna acción.No hay otro idioma disponible.No se han encontrado resultados.No se ha encontrado.No se ha encontrado.Nota (%d/%d)Notas (%s)Notas...AceptarAbrirAbrir filtrosAbrir registros relacionadosAbrir relaciónAbrir informeAbrir el calendarioAbrir registro Abrir...Abrir/Buscar relaciónOpcionesSobrescribir la definición de '%s'?Modo PDAImagen PNG (*.png)PegarPegar texto seleccionadoActivar el sonido del escáner de códigosPrevalidaciónPreferenciasPreferenciasPreferencias...PrevisualizarAnteriorRegistro anteriorEntrada anteriorPestaña anteriorImprimirImprimir informeImprimir...PerfilEditor de perfilesPerfil:SalirDelimitador de texto:Registro guardado.Los registros no se han eliminado.Registros eliminados.RelacionadoRelacionado...Entradas relacionadasRecargar/DeshacerEliminar "%s"Eliminar Eliminar archivoEliminar el perfil seleccionadoEliminar registro seleccionadoEliminar de las búsquedas favoritasInformesInformar del errorInforme...InformesRevisiónVersión:GuardarGuardar comoGuardar como...Guardar ancho de columnaGuardar estado de expansión del árbolGuardar y nuevoGuardar este registroGuardar vuestros cambiosEscanearBuscarBuscar %sPreferencias del límite de búsqueda_Límite de búsqueda...Buscar registro Menú de búsquedaMostrar la versión modificadaSeleccionarSeleccionar un archivoSeleccione un colorSeleccionar una versiónSeleccionar todoSeleccionar padreSeleccione su acciónSeleccionar…Seleccionar/Activar fila actualRegistros seleccionadosSelecciónEnviarEnviar un correo electrónico usando el registroAtajosMostrar registros activosMuestra las búsquedas favoritasMostrar registros inactivosMostrar como texto planoMostrar revisiones...Corrección ortográficaAsunto:CambiarCambiar vistaCambiar de vistaPlantillaTextoEntradas de textoTexto e iconosLa acción seleccionada requiere cerrar todas las pestañas. ¿Desea continuar?El numero de decimalesLos valores de "%s" no son válidos.Este registro ha sido modificado. ¿Desea guardarlo?Este registro ha sido modificado mientras lo editaba.A:HoyCommutar el menuConmutar filaConmutar selecciónDemasiadas peticiones. Inténtelo de nuevo más tarde.Barra de herramientasTraducir vistaTraducciónVerdaderoNo se ha podido comprobar si existe una nueva versión.No se ha podido completar la entrada de correo electrónicoNo se ha podido establecer el idioma %sNo se ha podido establecer el estado de expansión del árbolRecuperar registro seleccionado SubrayadoDesconocidoCabecera de columna desconocida: "%s"Desmarcar para eliminaciónDeseleccionar todoUsar formato localNombre de usuario:Nombre de usuario:ValorVer registros...Ver _registro...Semana¿Cuál es el nombre de esta exportación?Anchura:AsistenteAhora está trabajando en el/los registro(s) duplicado(s).Guardar de todas formasASíDebe elegir un registro.Primero debe elegir un archivo a importar.Debe guardar el registro antes de añadir traducciones.Su selección:_Acciones..._Añadir_Auto-detectar_Cancelar_Limpiar_Cerrar pestaña_Copiar la URL_Eliminar exportación_Eliminar..._Duplicar_Correo electrónico..._Exportar datos..._Importar datos..._NuevoSig_uiente_Notas..._AnteriorIm_primir..._Relacionado..._Recargar/Deshacer_Eliminar_Informes..._Guardar_Guardar exportación_BuscarCambiar vi_sta_URL ExportacióndModo desarrollodesactivar el uso de hilosretrocederir adelantehRegistra toda la información con nivel INFOmmodularidad, escalabilidad y seguridadaño próximoaño anteriorsIndica un archivo de configuración alternativoIndica el archivo a usar para la salida de información de logIndica el nivel de log: DEBUG, INFO, WARNING, ERROR, CRITICALIndica el usuarioIndica el nombre:puerto del servidorvSergi Almacellas AbellanaSa././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685598.0 tryton-7.0.24/tryton/data/locale/es/LC_MESSAGES/tryton.po0000644000175000017500000005127214517761236021100 0ustar00cedced# Spanish (Spain) translations for tryton. # Copyright (C) 2008 igor@tamarapatino.org # Copyright (C) 2009 PEMAS Servicios Profesionales, S.L. # Copyright (C) 2012 Zikzakmedia SL # This file is distributed under the same license as the tryton project. # FIRST AUTHOR , 2008. # Carlos Perelló Marín , 2008-2009. # Sergi Almacellas Abellana 2012. # Jordi Esteve Cusiné 2012. msgid "" msgstr "Content-Type: text/plain; charset=utf-8\n" msgid "specify alternate config file" msgstr "Indica un archivo de configuración alternativo" msgid "development mode" msgstr "Modo desarrollo" msgid "logging everything at INFO level" msgstr "Registra toda la información con nivel INFO" msgid "specify the log level: DEBUG, INFO, WARNING, ERROR, CRITICAL" msgstr "Indica el nivel de log: DEBUG, INFO, WARNING, ERROR, CRITICAL" msgid "specify the file used to output logging information" msgstr "Indica el archivo a usar para la salida de información de log" msgid "specify the login user" msgstr "Indica el usuario" msgid "specify the server hostname:port" msgstr "Indica el nombre:puerto del servidor" msgid "disable thread usage" msgstr "desactivar el uso de hilos" #, python-format msgid "Unable to set locale %s" msgstr "No se ha podido establecer el idioma %s" msgid ", " msgstr ", " msgid ",..." msgstr ",..." #, python-format msgid "%s (%s)" msgstr "%s (%s)" msgid "Select your action" msgstr "Seleccione su acción" msgid "No action defined." msgstr "No se ha definido ninguna acción." msgid "By: " msgstr "Por: " msgid "Selection" msgstr "Selección" msgid "Cancel" msgstr "Cancelar" msgid "OK" msgstr "Aceptar" msgid "Your selection:" msgstr "Su selección:" msgid "Save As..." msgstr "Guardar como..." msgid "Do you want to proceed?" msgstr "¿Desea continuar?" msgid "Always ignore this warning." msgstr "Ignorar siempre esta advertencia." msgid "No" msgstr "No" msgid "Yes" msgstr "Sí" msgid "Concurrency Exception" msgstr "Excepción de concurrencia" msgid "This record has been modified while you were editing it." msgstr "Este registro ha sido modificado mientras lo editaba." msgid "Cancel saving" msgstr "No guardes los cambios" msgid "Compare" msgstr "Comparar" msgid "See the modified version" msgstr "Mostrar la versión modificada" msgid "Write Anyway" msgstr "Guardar de todas formas" msgid "Save your current version" msgstr "Guardar vuestros cambios" #, python-format msgid "Compare: %s" msgstr "Comparar: %s" msgid "Close" msgstr "Cerrar" msgid "Application Error" msgstr "Error de aplicación" msgid "Report Bug" msgstr "Informar del error" #, python-format msgid "Check URL: %s" msgstr "URL de comprobación: %s" msgid "Unable to check for new version." msgstr "No se ha podido comprobar si existe una nueva versión." msgid "A new version is available!" msgstr "Esta disponible una nueva versión!" msgid "Download" msgstr "Descargar" msgid "Could not get a session." msgstr "No se ha podido obtener una sesión." msgid "Too many requests. Try again later." msgstr "Demasiadas peticiones. Inténtelo de nuevo más tarde." msgid "Not found." msgstr "No se ha encontrado." msgid "Not Found." msgstr "No se ha encontrado." msgid "..." msgstr "..." msgid "Search..." msgstr "Buscar..." msgid "Create..." msgstr "Crear..." #, python-format msgid "Create \"%s\"..." msgstr "Crear \"%s\"..." msgid "Value" msgstr "Valor" msgid "Displayed value" msgstr "Valor a mostrar" msgid "Format" msgstr "Formato" msgid "Display format" msgstr "Format a mostrar" msgid "Open the calendar" msgstr "Abrir el calendario" msgid "Date Format" msgstr "Formato fecha" msgid "Displayed date format" msgstr "Formato fecha actual" msgid "y" msgstr "a" msgid "True" msgstr "Verdadero" msgid "t" msgstr "v" msgid "False" msgstr "Falso" msgid "Digits" msgstr "Dígitos" msgid "The number of decimal" msgstr "El numero de decimales" msgid "Template" msgstr "Plantilla" msgid "Edit..." msgstr "Editar..." msgid "View Logs..." msgstr "Ver registros..." msgid "Attachments..." msgstr "Adjuntos..." msgid "Notes..." msgstr "Notas..." msgid "Actions..." msgstr "Acciones..." msgid "Relate..." msgstr "Relacionado..." msgid "Report..." msgstr "Informe..." msgid "Print..." msgstr "Imprimir..." msgid "E-Mail..." msgstr "Correo electrónico..." msgid "Bold" msgstr "Negrita" msgid "Italic" msgstr "Cursiva" msgid "Underline" msgstr "Subrayado" msgid "Align Left" msgstr "Alinear a la izquierda" msgid "Align Center" msgstr "Alinear en el centro" msgid "Align Right" msgstr "Alinear a la derecha" msgid "Justify" msgstr "Justificar" msgid "Foreground Color" msgstr "Color de fondo" msgid "Select a color" msgstr "Seleccione un color" msgid "Y" msgstr "A" msgid "M" msgstr "M" msgid "w" msgstr "S" msgid "d" msgstr "d" msgid "h" msgstr "h" msgid "m" msgstr "m" msgid "s" msgstr "s" msgid "Preferences..." msgstr "Preferencias..." msgid "Toolbar" msgstr "Barra de herramientas" msgid "Default" msgstr "Por defecto" msgid "Text and Icons" msgstr "Texto e iconos" msgid "Text" msgstr "Texto" msgid "Icons" msgstr "Iconos" msgid "Form" msgstr "Formulario" msgid "Save Column Width" msgstr "Guardar ancho de columna" msgid "Save Tree State" msgstr "Guardar estado de expansión del árbol" msgid "Spell Checking" msgstr "Corrección ortográfica" msgid "Play Sound for Code Scanner" msgstr "Activar el sonido del escáner de códigos" msgid "PDA Mode" msgstr "Modo PDA" msgid "Search Limit..." msgstr "_Límite de búsqueda..." msgid "Check Version" msgstr "Comprobar versión" msgid "Options" msgstr "Opciones" msgid "Documentation..." msgstr "Documentación..." msgid "Keyboard Shortcuts..." msgstr "Combinaciones de teclas..." msgid "About..." msgstr "Acerca de..." msgid "Help" msgstr "Ayuda" msgid "No result found." msgstr "No se han encontrado resultados." msgid "Favorites" msgstr "Favoritos" msgid "Manage..." msgstr "Gestionar..." msgid "Action" msgstr "Acción" msgid "" "The following action requires to close all tabs.\n" "Do you want to continue?" msgstr "" "La acción seleccionada requiere cerrar todas las pestañas.\n" "¿Desea continuar?" msgid "Application Shortcuts" msgstr "Atajos de la aplicación" msgid "Global" msgstr "Global" msgid "Preferences" msgstr "Preferencias" msgid "Search menu" msgstr "Menú de búsqueda" msgid "Toggle menu" msgstr "Commutar el menu" msgid "Previous tab" msgstr "Pestaña anterior" msgid "Next tab" msgstr "Pestaña siguiente" msgid "Shortcuts" msgstr "Atajos" msgid "Quit" msgstr "Salir" msgid "Edition Shortcuts" msgstr "Atajos de edición" msgid "Text Entries" msgstr "Entradas de texto" msgid "Cut selected text" msgstr "Cortar texto seleccionado" msgid "Copy selected text" msgstr "Copiar texto seleccionado" msgid "Paste copied text" msgstr "Pegar texto seleccionado" msgid "Next entry" msgstr "Siguiente entrada" msgid "Previous entry" msgstr "Entrada anterior" msgid "Relation Entries" msgstr "Entradas relacionadas" msgid "Create new relation" msgstr "Crear nueva relación" msgid "Open/Search relation" msgstr "Abrir/Buscar relación" msgid "List Entries" msgstr "Lista de entradas" msgid "Switch view" msgstr "Cambiar de vista" msgid "Create/Select new line" msgstr "Crear/Seleccionar línea nueva" msgid "Open relation" msgstr "Abrir relación" msgid "Mark line for deletion/removal" msgstr "Marcar linea para eliminar/suprimir" msgid "Mark line for removal" msgstr "Marcar para eliminar" msgid "Unmark line for deletion" msgstr "Desmarcar para eliminación" msgid "List/Tree Shortcuts" msgstr "Atajos de lista/árbol" msgid "Move Cursor" msgstr "Mover cursor" msgid "Move right" msgstr "Mover a la derecha" msgid "Move left" msgstr "Mover a la izquierda" msgid "Move up" msgstr "Mover arriba" msgid "Move down" msgstr "Mover abajo" msgid "Move up of one page" msgstr "Mover arriba una página" msgid "Move down of one page" msgstr "Mover abajo una página" msgid "Move to top" msgstr "Mover al principio" msgid "Move to bottom" msgstr "Mover al final" msgid "Move to parent" msgstr "Mover al padre" msgid "Edition" msgstr "Editar" msgid "Copy selected rows" msgstr "Copiar filas seleccionadas" msgid "Insert copied rows" msgstr "Insertar filas copiadas" msgid "Select all" msgstr "Seleccionar todo" msgid "Unselect all" msgstr "Deseleccionar todo" msgid "Select parent" msgstr "Seleccionar padre" msgid "Select/Activate current row" msgstr "Seleccionar/Activar fila actual" msgid "Toggle selection" msgstr "Conmutar selección" msgid "Expand/Collapse" msgstr "Expandir/Contraer" msgid "Expand row" msgstr "Expandir fila" msgid "Collapse row" msgstr "Contraer fila" msgid "Toggle row" msgstr "Conmutar fila" msgid "Collapse all rows" msgstr "Contraer todas las filas" msgid "Expand all rows" msgstr "Expandir todas las filas" msgid "Close Tab" msgstr "Cerrar pestaña" msgid "modularity, scalability and security" msgstr "modularidad, escalabilidad y seguridad" msgid "translator-credits" msgstr "Sergi Almacellas Abellana" #, python-format msgid "Attachments (%s)" msgstr "Adjuntos (%s)" msgid "Code Scanner" msgstr "Escáner de códigos" msgid "Code" msgstr "Código" msgid "Profile Editor" msgstr "Editor de perfiles" msgid "Profile" msgstr "Perfil" msgid "Add new profile" msgstr "Añadir un nuevo perfil" msgid "Remove selected profile" msgstr "Eliminar el perfil seleccionado" msgid "Host:" msgstr "Servidor:" msgid "Database:" msgstr "Base de datos:" msgid "Fetching databases list" msgstr "Recuperando lista de bases de datos" msgid "Username:" msgstr "Nombre de usuario:" msgid "Incompatible version of the server." msgstr "El cliente no es compatible con la versión del servidor." msgid "Could not connect to the server." msgstr "No se ha podido conectar con el servidor." msgid "Login" msgstr "Acceder" msgid "_Cancel" msgstr "_Cancelar" msgid "Cancel connection to the Tryton server" msgstr "Cancelar conexión con el servidor Tryton" msgid "C_onnect" msgstr "C_onectar" msgid "Connect the Tryton server" msgstr "Conectar con el servidor Tryton" msgid "Profile:" msgstr "Perfil:" msgid "Host / Database information" msgstr "Información del Servidor / Base de datos" msgid "User name:" msgstr "Nombre de usuario:" msgid "Unable to complete email entry" msgstr "No se ha podido completar la entrada de correo electrónico" #, python-format msgid "E-mail %s" msgstr "Correo electrónico %s" msgid "To:" msgstr "A:" msgid "Cc:" msgstr "Cc:" msgid "Bcc:" msgstr "CCo:" msgid "Subject:" msgstr "Asunto:" msgid "Body" msgstr "Mensaje" msgid "Reports" msgstr "Informes" msgid "Attachments" msgstr "Adjuntos" msgid "Files" msgstr "Archivos" msgid "Send" msgstr "Enviar" msgid "Select File" msgstr "Seleccionar un archivo" msgid "Remove File" msgstr "Eliminar archivo" msgid "Select" msgstr "Seleccionar" msgid "Add..." msgstr "Añadir..." msgid "Preview" msgstr "Previsualizar" msgid "Previous" msgstr "Anterior" msgid "Next" msgstr "Siguiente" #, python-format msgid "Attachment (%s)" msgstr "Adjuntos (%s)" #, python-format msgid "Note (%d/%d)" msgstr "Nota (%d/%d)" msgid "You have to select one record." msgstr "Debe elegir un registro." msgid "Are you sure to remove this record?" msgstr "¿Está seguro que quiere eliminar este registro?" msgid "Are you sure to remove those records?" msgstr "¿Está seguro que quiere eliminar estos registros?" msgid "Records not removed." msgstr "Los registros no se han eliminado." msgid "Records removed." msgstr "Registros eliminados." msgid "Working now on the duplicated record(s)." msgstr "Ahora está trabajando en el/los registro(s) duplicado(s)." msgid "Record saved." msgstr "Registro guardado." msgid "" "This record has been modified\n" "do you want to save it?" msgstr "" "Este registro ha sido modificado.\n" "¿Desea guardarlo?" msgid "Launch action" msgstr "Ejecutar acción" msgid "Relate" msgstr "Relacionado" msgid "Open related records" msgstr "Abrir registros relacionados" msgid "Report" msgstr "Informes" msgid "Open report" msgstr "Abrir informe" msgid "Print" msgstr "Imprimir" msgid "Print report" msgstr "Imprimir informe" msgid "_Copy URL" msgstr "_Copiar la URL" msgid "Copy URL into clipboard" msgstr "Copiar la URL al portapapeles" msgid "Unknown" msgstr "Desconocido" msgid "Limit" msgstr "Límite" msgid "Search Limit Settings" msgstr "Preferencias del límite de búsqueda" msgid "Limit:" msgstr "Límite:" #, python-format msgid "Logs (%s)" msgstr "Registros (%s)" msgid "Model:" msgstr "Modelo:" msgid "ID:" msgstr "ID:" msgid "Created by:" msgstr "Creado por:" msgid "Created at:" msgstr "Creado el:" msgid "Last Modified by:" msgstr "Última modificación por:" msgid "Last Modified at:" msgstr "Última modificación el:" #, python-format msgid "Notes (%s)" msgstr "Notas (%s)" msgid "Edit User Preferences" msgstr "Editar preferencias de usuario" msgid "Preference" msgstr "Preferencias" msgid "Revision" msgstr "Revisión" msgid "Select a revision" msgstr "Seleccionar una versión" msgid "Revision:" msgstr "Versión:" msgid "_Switch View" msgstr "Cambiar vi_sta" msgid "Switch View" msgstr "Cambiar vista" msgid "_Previous" msgstr "_Anterior" msgid "Previous Record" msgstr "Registro anterior" msgid "_Next" msgstr "Sig_uiente" msgid "Next Record" msgstr "Registro siguiente" msgid "_Search" msgstr "_Buscar" msgid "_New" msgstr "_Nuevo" msgid "Create a new record" msgstr "Crear un nuevo registro" msgid "_Save" msgstr "_Guardar" msgid "Save this record" msgstr "Guardar este registro" msgid "_Reload/Undo" msgstr "_Recargar/Deshacer" msgid "Reload/Undo" msgstr "Recargar/Deshacer" msgid "_Duplicate" msgstr "_Duplicar" msgid "_Delete..." msgstr "_Eliminar..." msgid "View _Logs..." msgstr "Ver _registro..." msgid "Show revisions..." msgstr "Mostrar revisiones..." msgid "A_ttachments..." msgstr "Adjun_tos..." msgid "Add an attachment to the record" msgstr "Añadir un adjunto al registro" msgid "_Notes..." msgstr "_Notas..." msgid "Add a note to the record" msgstr "Añadir una nota al registro" msgid "_Actions..." msgstr "_Acciones..." msgid "_Relate..." msgstr "_Relacionado..." msgid "_Report..." msgstr "_Informes..." msgid "_Print..." msgstr "Im_primir..." msgid "_E-Mail..." msgstr "_Correo electrónico..." msgid "Send an e-mail using the record" msgstr "Enviar un correo electrónico usando el registro" msgid "_Export Data..." msgstr "_Exportar datos..." msgid "_Import Data..." msgstr "_Importar datos..." msgid "Copy _URL..." msgstr "Copiar _URL..." msgid "_Close Tab" msgstr "_Cerrar pestaña" msgid "All fields" msgstr "Todos los campos" msgid "_Add" msgstr "_Añadir" msgid "_Remove" msgstr "_Eliminar" msgid "_Clear" msgstr "_Limpiar" msgid "Fields selected" msgstr "Campos seleccionados" msgid "CSV Parameters" msgstr "Parámetros CSV" msgid "Delimiter:" msgstr "Separador:" msgid "Quote char:" msgstr "Delimitador de texto:" msgid "Encoding:" msgstr "Codificación:" msgid "Use locale format" msgstr "Usar formato local" msgid "Field name" msgstr "Nombre del campo" #, python-format msgid "CSV Export: %s" msgstr "Exportación CSV: %s" msgid "_Save Export" msgstr "_Guardar exportación" msgid "_URL Export" msgstr "_URL Exportación" msgid "_Delete Export" msgstr "_Eliminar exportación" msgid "Predefined exports" msgstr "Exportaciones predeterminadas" msgid "Name" msgstr "Nombre" msgid "Open" msgstr "Abrir" msgid "Save" msgstr "Guardar" msgid "Listed Records" msgstr "Registros listados" msgid "Selected Records" msgstr "Registros seleccionados" msgid "Ignore search limit" msgstr "Ignorar límite de búsqueda" msgid "Add field names" msgstr "Añadir nombres de campo" #, python-format msgid "%s (string)" msgstr "%s (cadena)" #, python-format msgid "%s (model name)" msgstr "%s (nombre del modelo)" #, python-format msgid "%s/Record Name" msgstr "%s/Nombre del registro" msgid "What is the name of this export?" msgstr "¿Cuál es el nombre de esta exportación?" #, python-format msgid "Override '%s' definition?" msgstr "Sobrescribir la definición de '%s'?" #, python-format msgid "%d record saved." msgstr "Se ha guardado %d registro." #, python-format msgid "%d records saved." msgstr "Se han guardado %d registros." msgid "Export failed" msgstr "Ha fallado la exportación" msgid "Link" msgstr "Enlace" msgid "Delete" msgstr "Eliminar" msgid "Discard changes" msgstr "Descartar cambios" msgid "Save and New" msgstr "Guardar y nuevo" msgid "Add and New" msgstr "Añadir y nuevo" msgid "Add" msgstr "Añadir" msgid "Apply changes" msgstr "Aplicar cambios" msgid "Switch" msgstr "Cambiar" msgid "Remove " msgstr "Eliminar " msgid "Create a new record " msgstr "Crear un nuevo registro " msgid "Delete selected record " msgstr "Eliminar registro seleccionado " msgid "Undelete selected record " msgstr "Recuperar registro seleccionado " #, python-format msgid "CSV Import: %s" msgstr "Importación CSV: %s" msgid "_Auto-Detect" msgstr "_Auto-detectar" msgid "File to Import:" msgstr "Archivo a importar:" msgid "Open..." msgstr "Abrir..." msgid "Lines to Skip:" msgstr "Líneas a omitir:" msgid "You must select an import file first." msgstr "Primero debe elegir un archivo a importar." msgid "Detection failed" msgstr "Ha fallado la detección" #, python-format msgid "Unknown column header \"%s\"" msgstr "Cabecera de columna desconocida: \"%s\"" msgid "Error" msgstr "Error" msgid "Import failed" msgstr "Ha fallado la importación" #, python-format msgid "%d record imported." msgstr "Se ha importado %d registro." #, python-format msgid "%d records imported." msgstr "Se han importado %d registros." msgid "Search" msgstr "Buscar" msgid "New" msgstr "Nuevo" #, python-format msgid "Search %s" msgstr "Buscar %s" msgid "Wizard" msgstr "Asistente" msgid "ID" msgstr "ID" msgid "Created by" msgstr "Creado por" msgid "Created at" msgstr "Creado el" msgid "Edited by" msgstr "Editado por" msgid "Edited at" msgstr "Editado el" msgid "Unable to set view tree state" msgstr "No se ha podido establecer el estado de expansión del árbol" #, python-format msgid "\"%s\" is not valid according to its domain." msgstr "\"%s\" no es válido según su dominio." #, python-format msgid "\"%s\" is required." msgstr "\"%s\" es obligatorio." #, python-format msgid "The values of \"%s\" are not valid." msgstr "Los valores de \"%s\" no son válidos." msgid "Pre-validation" msgstr "Prevalidación" msgid ":" msgstr ":" msgid "Scan" msgstr "Escanear" msgid "Image Size" msgstr "Tamaño de la imagen" msgid "Width:" msgstr "Anchura:" msgid "Height:" msgstr "Altura:" msgid "PNG image (*.png)" msgstr "Imagen PNG (*.png)" msgid "Save As" msgstr "Guardar como" msgid "Image size too large." msgstr "El tamaño de la imagen es muy grande." msgid "Copy" msgstr "Copiar" msgid "Paste" msgstr "Pegar" msgid ".." msgstr ".." msgid "Open filters" msgstr "Abrir filtros" msgid "Show bookmarks of filters" msgstr "Muestra las búsquedas favoritas" msgid "Remove this bookmark" msgstr "Eliminar de las búsquedas favoritas" msgid "Bookmark this filter" msgstr "Guardar como búsqueda favorita" msgid "Show active records" msgstr "Mostrar registros activos" msgid "Show inactive records" msgstr "Mostrar registros inactivos" msgid "Bookmark Name:" msgstr "Nombre de la búsqueda favorita:" msgid "Find" msgstr "Buscar" msgid "Today" msgstr "Hoy" msgid "go back" msgstr "retroceder" msgid "go forward" msgstr "ir adelante" msgid "previous year" msgstr "año anterior" msgid "next year" msgstr "año próximo" msgid "Day" msgstr "Día" msgid "Week" msgstr "Semana" msgid "Month" msgstr "Mes" msgid "Select..." msgstr "Seleccionar…" msgid "Clear" msgstr "Limpiar" msgid "All files" msgstr "Todos los archivos" msgid "Show plain text" msgstr "Mostrar como texto plano" msgid "Add value" msgstr "Añadir un valor" #, python-format msgid "Remove \"%s\"" msgstr "Eliminar \"%s\"" msgid "Images" msgstr "Imágenes" msgid "Add existing record" msgstr "Añadir un registro existente" msgid "Remove selected record" msgstr "Eliminar registro seleccionado" msgid "Open the record " msgstr "Abrir registro " msgid "Clear the field " msgstr "Eliminar el registro " msgid "Search a record " msgstr "Buscar registro " msgid "Edit selected record" msgstr "Editar registro seleccionado" msgid "Delete selected record" msgstr "Eliminar registro seleccionado" #, python-format msgid "%s%%" msgstr "%s%%" msgid "Choose a language" msgstr "Escoja un lenguaje" msgid "Translation" msgstr "Traducción" msgid "Edit" msgstr "Editar" msgid "Fuzzy" msgstr "Dudoso" msgid "You need to save the record before adding translations." msgstr "Debe guardar el registro antes de añadir traducciones." msgid "No other language available." msgstr "No hay otro idioma disponible." msgid "#ERROR" msgstr "#ERROR" msgid "Translate view" msgstr "Traducir vista" ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1745680285.1241102 tryton-7.0.24/tryton/data/locale/es_419/0000755000175000017500000000000015003173635015771 5ustar00cedced././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1745680285.1641107 tryton-7.0.24/tryton/data/locale/es_419/LC_MESSAGES/0000755000175000017500000000000015003173635017556 5ustar00cedced././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1745680279.0 tryton-7.0.24/tryton/data/locale/es_419/LC_MESSAGES/tryton.mo0000644000175000017500000001570415003173627021462 0ustar00cedced   ? F b #t %          ) / 5 ? G ] ~        $ . 4 : D J O U Z ] a u #            = H K P e w          " 7 B J S X ` k |         !3< COIX58#?N S]o t( %7Uegikmo!'+-5=Z y!13!*/ 7Xx~)3C W bn w    '-04Q9l !4"7Zy  ):"Mp $  '=X _k0,4< MOW456P _ is* :*47_, ,......:ActionAddAdd a note to the recordAdd an attachment to the recordAdd...Always ignore this warning.Application ErrorAre you sure to remove this record?Are you sure to remove those records?AttachmentsBcc:BoldBookmark Name:Bookmark this filterBy: CSV ParametersCancelCc:Choose a languageClearCloseClose TabCompareConcurrency ExceptionCould not connect to the server.Create a new recordCreated atCreated at:Created byCreated by:DeleteDelimiter:Detection failedDo you want to proceed?Documentation...DownloadE-Mail...EditEncoding:ErrorFalseFavoritesFilesFindFuzzyHelpIDID:Ignore search limitImport failedIncompatible version of the server.ItalicLaunch actionLines to Skip:Listed RecordsLoginMManage...NewNextNext RecordNext tabNoNo action defined.No other language available.Not found.OKOpenOpen related recordsOpen the calendarOpen...Pre-validationPreviewPreviousPrevious RecordPrevious tabPrintPrint reportRecord saved.Records not removed.Records removed.RelateReload/UndoRemove FileRemove this bookmarkReport BugReportsRevisionSaveSave AsSave As...Save this recordSave your current versionSearchSelectSelect your actionSelect...Selected RecordsSendSend an e-mail using the recordShow active recordsShow inactive recordsShow revisions...Subject:SwitchSwitch viewTemplateThe following action requires to close all tabs. Do you want to continue?This record has been modified do you want to save it?This record has been modified while you were editing it.To:TodayToo many requests. Try again later.Translate viewTrueUnderlineUse locale formatWeekWhat is the name of this export?WizardWorking now on the duplicated record(s).Write AnywayYYesYou have to select one record.You must select an import file first.You need to save the record before adding translations.Your selection:dhmsttranslator-creditswyProject-Id-Version: PROJECT VERSION Report-Msgid-Bugs-To: EMAIL@ADDRESS POT-Creation-Date: 2025-04-26 17:11+0200 PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE Last-Translator: FULL NAME Language: es_419 Language-Team: es_419 Plural-Forms: nplurals=2; plural=(n != 1); MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: 8bit Generated-By: Babel 2.17.0 , ,.......:AcciónAgregarAñadir una nota al registroAñadir un adjunto al registroAñadir...Ignorar siempre esta advertencia.Error de aplicación¿Está seguro que quiere eliminar este registro?¿Está seguro que quiere eliminar estos registros?AdjuntosCCo:NegritaNombre de la búsqueda favorita:Guardar como búsqueda favoritaPor: Parámetros CSVCancelarCc:Escoja un lenguajeLimpiarCerrarCerrar pestañaCompararExcepción de concurrenciaNo se ha podido conectar con el servidor.Crear un nuevo registroFecha creaciónFecha de creación:Creado porCreado por:EliminarSeparador:Ha fallado la detección¿Desea continuar?Documentación...DescargarCorreo electrónico...EditarCodificación:ErrorFalsoFavoritosArchivosBuscarRevisiónAyudaIDID:Ignorar límite de búsquedaHa fallado la importaciónEl cliente no es compatible con la versión del servidor.CursivaEjecutar acciónLíneas a omitir:Registros listadosIngresarMAdministrar...NuevoSiguienteRegistro siguientePestaña siguienteNoNo se ha definido ninguna acción.No hay otro idioma disponible.No se ha encontrado.AceptarAbrirAbrir registros relacionadosAbrir el calendarioAbrir...PrevalidaciónPrevisualizarAnteriorRegistro anteriorPestaña anteriorImprimirImprimir informeRegistro guardado.Los registros no se han eliminado.Registros eliminados.RelacionadoRecargar/DeshacerEliminar archivoEliminar de las búsquedas favoritasInformar del errorInformesRevisiónGuardarGuardar cómoGuardar cómo...Guardar este registroGuardar tu versión actualBuscarSeleccionarSeleccione su acciónSeleccionar...Registros seleccionadosEnviarEnviar un correo electrónico usando el registroMostrar registros activosMostrar registros inactivosMostrar revisiones...Asunto:CambiarCambiar de vistaPlantillaLa acción seleccionada requiere cerrar todas las pestañas. ¿Desea continuar?Este registro ha sido modificado. ¿Desea guardarlo?Este registro ha sido modificado mientras lo editaba.A:HoyDemasiadas peticiones. Inténtalo de nuevo más tarde.Traducir vistaVerdaderoSubrayadoUsar formato localSemana¿Cuál es el nombre de esta exportación?AsistenteAhora está trabajando en el/los registro(s) duplicado(s).Guardar de todas formasASíDebe elegir un registro.Primero debe elegir un archivo a importar.Debe guardar el registro antes de añadir traducciones.Su selección:dhmsvSebastian MarroSa././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685599.0 tryton-7.0.24/tryton/data/locale/es_419/LC_MESSAGES/tryton.po0000644000175000017500000003755714517761237021510 0ustar00cedced# Translations template for tryton. # Copyright (C) 2016 Tryton # This file is distributed under the same license as the tryton project. # FIRST AUTHOR , 2016. msgid "" msgstr "Content-Type: text/plain; charset=utf-8\n" msgid "specify alternate config file" msgstr "" msgid "development mode" msgstr "" msgid "logging everything at INFO level" msgstr "" msgid "specify the log level: DEBUG, INFO, WARNING, ERROR, CRITICAL" msgstr "" msgid "specify the file used to output logging information" msgstr "" msgid "specify the login user" msgstr "" msgid "specify the server hostname:port" msgstr "" msgid "disable thread usage" msgstr "" #, python-format msgid "Unable to set locale %s" msgstr "" msgid ", " msgstr ", " msgid ",..." msgstr ",...." #, python-format msgid "%s (%s)" msgstr "" msgid "Select your action" msgstr "Seleccione su acción" msgid "No action defined." msgstr "No se ha definido ninguna acción." msgid "By: " msgstr "Por: " msgid "Selection" msgstr "" msgid "Cancel" msgstr "Cancelar" msgid "OK" msgstr "Aceptar" msgid "Your selection:" msgstr "Su selección:" msgid "Save As..." msgstr "Guardar cómo..." msgid "Do you want to proceed?" msgstr "¿Desea continuar?" msgid "Always ignore this warning." msgstr "Ignorar siempre esta advertencia." msgid "No" msgstr "No" msgid "Yes" msgstr "Sí" msgid "Concurrency Exception" msgstr "Excepción de concurrencia" msgid "This record has been modified while you were editing it." msgstr "Este registro ha sido modificado mientras lo editaba." msgid "Cancel saving" msgstr "" msgid "Compare" msgstr "Comparar" msgid "See the modified version" msgstr "" msgid "Write Anyway" msgstr "Guardar de todas formas" msgid "Save your current version" msgstr "Guardar tu versión actual" #, python-format msgid "Compare: %s" msgstr "" msgid "Close" msgstr "Cerrar" msgid "Application Error" msgstr "Error de aplicación" msgid "Report Bug" msgstr "Informar del error" #, python-format msgid "Check URL: %s" msgstr "" msgid "Unable to check for new version." msgstr "" msgid "A new version is available!" msgstr "" msgid "Download" msgstr "Descargar" msgid "Could not get a session." msgstr "" msgid "Too many requests. Try again later." msgstr "Demasiadas peticiones. Inténtalo de nuevo más tarde." msgid "Not found." msgstr "No se ha encontrado." msgid "Not Found." msgstr "" msgid "..." msgstr "..." msgid "Search..." msgstr "" msgid "Create..." msgstr "" #, python-format msgid "Create \"%s\"..." msgstr "" msgid "Value" msgstr "" msgid "Displayed value" msgstr "" msgid "Format" msgstr "" msgid "Display format" msgstr "" msgid "Open the calendar" msgstr "Abrir el calendario" msgid "Date Format" msgstr "" msgid "Displayed date format" msgstr "" msgid "y" msgstr "a" msgid "True" msgstr "Verdadero" msgid "t" msgstr "v" msgid "False" msgstr "Falso" msgid "Digits" msgstr "" msgid "The number of decimal" msgstr "" msgid "Template" msgstr "Plantilla" msgid "Edit..." msgstr "" msgid "View Logs..." msgstr "" msgid "Attachments..." msgstr "" msgid "Notes..." msgstr "" msgid "Actions..." msgstr "" msgid "Relate..." msgstr "" msgid "Report..." msgstr "" msgid "Print..." msgstr "" msgid "E-Mail..." msgstr "Correo electrónico..." msgid "Bold" msgstr "Negrita" msgid "Italic" msgstr "Cursiva" msgid "Underline" msgstr "Subrayado" msgid "Align Left" msgstr "" msgid "Align Center" msgstr "" msgid "Align Right" msgstr "" msgid "Justify" msgstr "" msgid "Foreground Color" msgstr "" msgid "Select a color" msgstr "" msgid "Y" msgstr "A" msgid "M" msgstr "M" msgid "w" msgstr "S" msgid "d" msgstr "d" msgid "h" msgstr "h" msgid "m" msgstr "m" msgid "s" msgstr "s" msgid "Preferences..." msgstr "" msgid "Toolbar" msgstr "" msgid "Default" msgstr "" msgid "Text and Icons" msgstr "" msgid "Text" msgstr "" msgid "Icons" msgstr "" msgid "Form" msgstr "" msgid "Save Column Width" msgstr "" msgid "Save Tree State" msgstr "" msgid "Spell Checking" msgstr "" msgid "Play Sound for Code Scanner" msgstr "" msgid "PDA Mode" msgstr "" msgid "Search Limit..." msgstr "" msgid "Check Version" msgstr "" msgid "Options" msgstr "" msgid "Documentation..." msgstr "Documentación..." msgid "Keyboard Shortcuts..." msgstr "" msgid "About..." msgstr "" msgid "Help" msgstr "Ayuda" msgid "No result found." msgstr "" msgid "Favorites" msgstr "Favoritos" msgid "Manage..." msgstr "Administrar..." msgid "Action" msgstr "Acción" msgid "" "The following action requires to close all tabs.\n" "Do you want to continue?" msgstr "" "La acción seleccionada requiere cerrar todas las pestañas.\n" "¿Desea continuar?" msgid "Application Shortcuts" msgstr "" msgid "Global" msgstr "" msgid "Preferences" msgstr "" msgid "Search menu" msgstr "" msgid "Toggle menu" msgstr "" msgid "Previous tab" msgstr "Pestaña anterior" msgid "Next tab" msgstr "Pestaña siguiente" msgid "Shortcuts" msgstr "" msgid "Quit" msgstr "" msgid "Edition Shortcuts" msgstr "" msgid "Text Entries" msgstr "" msgid "Cut selected text" msgstr "" msgid "Copy selected text" msgstr "" msgid "Paste copied text" msgstr "" msgid "Next entry" msgstr "" msgid "Previous entry" msgstr "" msgid "Relation Entries" msgstr "" msgid "Create new relation" msgstr "" msgid "Open/Search relation" msgstr "" msgid "List Entries" msgstr "" msgid "Switch view" msgstr "Cambiar de vista" msgid "Create/Select new line" msgstr "" msgid "Open relation" msgstr "" msgid "Mark line for deletion/removal" msgstr "" msgid "Mark line for removal" msgstr "" msgid "Unmark line for deletion" msgstr "" msgid "List/Tree Shortcuts" msgstr "" msgid "Move Cursor" msgstr "" msgid "Move right" msgstr "" msgid "Move left" msgstr "" msgid "Move up" msgstr "" msgid "Move down" msgstr "" msgid "Move up of one page" msgstr "" msgid "Move down of one page" msgstr "" msgid "Move to top" msgstr "" msgid "Move to bottom" msgstr "" msgid "Move to parent" msgstr "" #, fuzzy msgid "Edition" msgstr "Editar" msgid "Copy selected rows" msgstr "" msgid "Insert copied rows" msgstr "" msgid "Select all" msgstr "" msgid "Unselect all" msgstr "" msgid "Select parent" msgstr "" msgid "Select/Activate current row" msgstr "" msgid "Toggle selection" msgstr "" msgid "Expand/Collapse" msgstr "" msgid "Expand row" msgstr "" msgid "Collapse row" msgstr "" msgid "Toggle row" msgstr "" msgid "Collapse all rows" msgstr "" msgid "Expand all rows" msgstr "" msgid "Close Tab" msgstr "Cerrar pestaña" msgid "modularity, scalability and security" msgstr "" msgid "translator-credits" msgstr "Sebastian Marro" #, python-format msgid "Attachments (%s)" msgstr "" msgid "Code Scanner" msgstr "" msgid "Code" msgstr "" msgid "Profile Editor" msgstr "" msgid "Profile" msgstr "" msgid "Add new profile" msgstr "" msgid "Remove selected profile" msgstr "" msgid "Host:" msgstr "" msgid "Database:" msgstr "" msgid "Fetching databases list" msgstr "" msgid "Username:" msgstr "" msgid "Incompatible version of the server." msgstr "El cliente no es compatible con la versión del servidor." msgid "Could not connect to the server." msgstr "No se ha podido conectar con el servidor." msgid "Login" msgstr "Ingresar" msgid "_Cancel" msgstr "" msgid "Cancel connection to the Tryton server" msgstr "" msgid "C_onnect" msgstr "" msgid "Connect the Tryton server" msgstr "" msgid "Profile:" msgstr "" msgid "Host / Database information" msgstr "" msgid "User name:" msgstr "" msgid "Unable to complete email entry" msgstr "" #, python-format msgid "E-mail %s" msgstr "" msgid "To:" msgstr "A:" msgid "Cc:" msgstr "Cc:" msgid "Bcc:" msgstr "CCo:" msgid "Subject:" msgstr "Asunto:" msgid "Body" msgstr "" msgid "Reports" msgstr "Informes" msgid "Attachments" msgstr "Adjuntos" msgid "Files" msgstr "Archivos" msgid "Send" msgstr "Enviar" msgid "Select File" msgstr "" msgid "Remove File" msgstr "Eliminar archivo" msgid "Select" msgstr "Seleccionar" msgid "Add..." msgstr "Añadir..." msgid "Preview" msgstr "Previsualizar" msgid "Previous" msgstr "Anterior" msgid "Next" msgstr "Siguiente" #, python-format msgid "Attachment (%s)" msgstr "" #, python-format msgid "Note (%d/%d)" msgstr "" msgid "You have to select one record." msgstr "Debe elegir un registro." msgid "Are you sure to remove this record?" msgstr "¿Está seguro que quiere eliminar este registro?" msgid "Are you sure to remove those records?" msgstr "¿Está seguro que quiere eliminar estos registros?" msgid "Records not removed." msgstr "Los registros no se han eliminado." msgid "Records removed." msgstr "Registros eliminados." msgid "Working now on the duplicated record(s)." msgstr "Ahora está trabajando en el/los registro(s) duplicado(s)." msgid "Record saved." msgstr "Registro guardado." msgid "" "This record has been modified\n" "do you want to save it?" msgstr "" "Este registro ha sido modificado.\n" "¿Desea guardarlo?" msgid "Launch action" msgstr "Ejecutar acción" msgid "Relate" msgstr "Relacionado" msgid "Open related records" msgstr "Abrir registros relacionados" msgid "Report" msgstr "" msgid "Open report" msgstr "" msgid "Print" msgstr "Imprimir" msgid "Print report" msgstr "Imprimir informe" msgid "_Copy URL" msgstr "" msgid "Copy URL into clipboard" msgstr "" msgid "Unknown" msgstr "" msgid "Limit" msgstr "" msgid "Search Limit Settings" msgstr "" msgid "Limit:" msgstr "" #, python-format msgid "Logs (%s)" msgstr "" msgid "Model:" msgstr "" msgid "ID:" msgstr "ID:" msgid "Created by:" msgstr "Creado por:" msgid "Created at:" msgstr "Fecha de creación:" msgid "Last Modified by:" msgstr "" msgid "Last Modified at:" msgstr "" #, python-format msgid "Notes (%s)" msgstr "" msgid "Edit User Preferences" msgstr "" msgid "Preference" msgstr "" msgid "Revision" msgstr "Revisión" msgid "Select a revision" msgstr "" msgid "Revision:" msgstr "" msgid "_Switch View" msgstr "" msgid "Switch View" msgstr "" msgid "_Previous" msgstr "" msgid "Previous Record" msgstr "Registro anterior" msgid "_Next" msgstr "" msgid "Next Record" msgstr "Registro siguiente" msgid "_Search" msgstr "" msgid "_New" msgstr "" msgid "Create a new record" msgstr "Crear un nuevo registro" msgid "_Save" msgstr "" msgid "Save this record" msgstr "Guardar este registro" msgid "_Reload/Undo" msgstr "" msgid "Reload/Undo" msgstr "Recargar/Deshacer" msgid "_Duplicate" msgstr "" msgid "_Delete..." msgstr "" msgid "View _Logs..." msgstr "" msgid "Show revisions..." msgstr "Mostrar revisiones..." msgid "A_ttachments..." msgstr "" msgid "Add an attachment to the record" msgstr "Añadir un adjunto al registro" msgid "_Notes..." msgstr "" msgid "Add a note to the record" msgstr "Añadir una nota al registro" msgid "_Actions..." msgstr "" msgid "_Relate..." msgstr "" msgid "_Report..." msgstr "" msgid "_Print..." msgstr "" msgid "_E-Mail..." msgstr "" msgid "Send an e-mail using the record" msgstr "Enviar un correo electrónico usando el registro" msgid "_Export Data..." msgstr "" msgid "_Import Data..." msgstr "" msgid "Copy _URL..." msgstr "" msgid "_Close Tab" msgstr "" msgid "All fields" msgstr "" msgid "_Add" msgstr "" msgid "_Remove" msgstr "" msgid "_Clear" msgstr "" msgid "Fields selected" msgstr "" msgid "CSV Parameters" msgstr "Parámetros CSV" msgid "Delimiter:" msgstr "Separador:" msgid "Quote char:" msgstr "" msgid "Encoding:" msgstr "Codificación:" msgid "Use locale format" msgstr "Usar formato local" msgid "Field name" msgstr "" #, python-format msgid "CSV Export: %s" msgstr "" msgid "_Save Export" msgstr "" msgid "_URL Export" msgstr "" msgid "_Delete Export" msgstr "" msgid "Predefined exports" msgstr "" msgid "Name" msgstr "" msgid "Open" msgstr "Abrir" msgid "Save" msgstr "Guardar" msgid "Listed Records" msgstr "Registros listados" msgid "Selected Records" msgstr "Registros seleccionados" msgid "Ignore search limit" msgstr "Ignorar límite de búsqueda" msgid "Add field names" msgstr "" #, python-format msgid "%s (string)" msgstr "" #, python-format msgid "%s (model name)" msgstr "" #, python-format msgid "%s/Record Name" msgstr "" msgid "What is the name of this export?" msgstr "¿Cuál es el nombre de esta exportación?" #, python-format msgid "Override '%s' definition?" msgstr "" #, python-format msgid "%d record saved." msgstr "" #, python-format msgid "%d records saved." msgstr "" msgid "Export failed" msgstr "" msgid "Link" msgstr "" msgid "Delete" msgstr "Eliminar" msgid "Discard changes" msgstr "" msgid "Save and New" msgstr "" msgid "Add and New" msgstr "" msgid "Add" msgstr "Agregar" msgid "Apply changes" msgstr "" msgid "Switch" msgstr "Cambiar" msgid "Remove " msgstr "" msgid "Create a new record " msgstr "" msgid "Delete selected record " msgstr "" msgid "Undelete selected record " msgstr "" #, python-format msgid "CSV Import: %s" msgstr "" msgid "_Auto-Detect" msgstr "" msgid "File to Import:" msgstr "" msgid "Open..." msgstr "Abrir..." msgid "Lines to Skip:" msgstr "Líneas a omitir:" msgid "You must select an import file first." msgstr "Primero debe elegir un archivo a importar." msgid "Detection failed" msgstr "Ha fallado la detección" #, python-format msgid "Unknown column header \"%s\"" msgstr "" msgid "Error" msgstr "Error" msgid "Import failed" msgstr "Ha fallado la importación" #, python-format msgid "%d record imported." msgstr "" #, python-format msgid "%d records imported." msgstr "" msgid "Search" msgstr "Buscar" msgid "New" msgstr "Nuevo" #, python-format msgid "Search %s" msgstr "" msgid "Wizard" msgstr "Asistente" msgid "ID" msgstr "ID" msgid "Created by" msgstr "Creado por" msgid "Created at" msgstr "Fecha creación" #, fuzzy msgid "Edited by" msgstr "Editar" #, fuzzy msgid "Edited at" msgstr "Editar" msgid "Unable to set view tree state" msgstr "" #, python-format msgid "\"%s\" is not valid according to its domain." msgstr "" #, python-format msgid "\"%s\" is required." msgstr "" #, python-format msgid "The values of \"%s\" are not valid." msgstr "" msgid "Pre-validation" msgstr "Prevalidación" msgid ":" msgstr ":" msgid "Scan" msgstr "" msgid "Image Size" msgstr "" msgid "Width:" msgstr "" msgid "Height:" msgstr "" msgid "PNG image (*.png)" msgstr "" msgid "Save As" msgstr "Guardar cómo" msgid "Image size too large." msgstr "" msgid "Copy" msgstr "" msgid "Paste" msgstr "" msgid ".." msgstr "" msgid "Open filters" msgstr "" msgid "Show bookmarks of filters" msgstr "" msgid "Remove this bookmark" msgstr "Eliminar de las búsquedas favoritas" msgid "Bookmark this filter" msgstr "Guardar como búsqueda favorita" msgid "Show active records" msgstr "Mostrar registros activos" msgid "Show inactive records" msgstr "Mostrar registros inactivos" msgid "Bookmark Name:" msgstr "Nombre de la búsqueda favorita:" msgid "Find" msgstr "Buscar" msgid "Today" msgstr "Hoy" msgid "go back" msgstr "" msgid "go forward" msgstr "" msgid "previous year" msgstr "" msgid "next year" msgstr "" msgid "Day" msgstr "" msgid "Week" msgstr "Semana" msgid "Month" msgstr "" msgid "Select..." msgstr "Seleccionar..." msgid "Clear" msgstr "Limpiar" msgid "All files" msgstr "" msgid "Show plain text" msgstr "" msgid "Add value" msgstr "" #, python-format msgid "Remove \"%s\"" msgstr "" msgid "Images" msgstr "" msgid "Add existing record" msgstr "" msgid "Remove selected record" msgstr "" msgid "Open the record " msgstr "" msgid "Clear the field " msgstr "" msgid "Search a record " msgstr "" msgid "Edit selected record" msgstr "" msgid "Delete selected record" msgstr "" #, python-format msgid "%s%%" msgstr "" msgid "Choose a language" msgstr "Escoja un lenguaje" msgid "Translation" msgstr "" msgid "Edit" msgstr "Editar" msgid "Fuzzy" msgstr "Revisión" msgid "You need to save the record before adding translations." msgstr "Debe guardar el registro antes de añadir traducciones." msgid "No other language available." msgstr "No hay otro idioma disponible." msgid "#ERROR" msgstr "" msgid "Translate view" msgstr "Traducir vista" ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1745680285.1241102 tryton-7.0.24/tryton/data/locale/et/0000755000175000017500000000000015003173635015375 5ustar00cedced././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1745680285.1641107 tryton-7.0.24/tryton/data/locale/et/LC_MESSAGES/0000755000175000017500000000000015003173635017162 5ustar00cedced././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1745680279.0 tryton-7.0.24/tryton/data/locale/et/LC_MESSAGES/tryton.mo0000644000175000017500000003224615003173627021066 0ustar00cedced&L |} 'ARc   # 0 ; GQm#%#8=L[js&z      (0F`e }   5@Oeu     $49JOV\ckp     ( /9 O Yd s    %.1 6C Xdv    +8 >KT\kt y   , 3 >H Q[` hs ) ; FT gq   &27IF58  ,1I hr z    % 7- e u       ! ! ! ! &! 0!;! C!N! T!a! i!v!x!! !! !! ! !!!<!6" M"n"p"r"t"$$/$E$\$d$ t$$$$$$$$($ $$% %)% 1%>%C%V%i%%%%%%%% %%&&$1&&V& }& & &&&&&&&& ''"'A'W'j' ~' ''''' '''' ((-(=( S(a(p( (( ((( (((( )) 0) >)H)N)g)|))))))) ))))))) * ***3*;*>*A* I*V*j*q* y****** * **** ***+ +*+ :+F+\+a+e+k+ {++++++++ , ,, , , 2, >,K,^,e, m,x,,, ,, , ,, ,,,-- - -(-:-C-J-]-t-- - - - ----. . . #. /.<.E.U.h.z......../ // %/ 0/ ;/H/P/d/j/s////// 00 0&0,0O=0/00000 0111+1 F1S1\1k11 1 1111 1111 1$ 2,.2 [2 g2u2{2 2 22 22 2 2 22 33!3 (363 ?3 J3U3 ^3 j3t3333 3 3 33333 33*3<)4%f44444%d record imported.%d record saved.%d records imported.%d records saved.%s (%s)%s (model name)%s (string)%s%%, .....:All fieldsFields selectedPredefined exportsCreate...Search...A new version is available!A_ttachments...ActionActions...AddAdd a note to the recordAdd an attachment to the recordAdd existing recordAdd field namesAdd new profileAdd valueAdd...Align CenterAlign LeftAlign RightAll filesAlways ignore this warning.Application ErrorApplication ShortcutsAre you sure to remove this record?Are you sure to remove those records?Attachment (%s)Attachments (%s)Attachments...BoldBookmark Name:Bookmark this filterBy: CSV Export: %sCSV Import: %sCSV ParametersC_onnectCancelCancel connection to the Tryton serverCancel savingCheck URL: %sCheck VersionChoose a languageClearClear the field CloseClose TabCollapse all rowsCollapse rowCompareConcurrency ExceptionConnect the Tryton serverCopyCopy URL into clipboardCopy _URL...Copy selected textCreate a new recordCreate a new record Cut selected textDatabase:Date FormatDefaultDeleteDelete selected recordDelete selected record Delimiter:Display formatDisplayed date formatDisplayed valueDo you want to proceed?DownloadE-Mail...EditEdit User PreferencesEdit selected recordEdit...ErrorExpand all rowsExpand rowExpand/CollapseFalseFavoritesField nameFile to Import:FindForeground ColorFormFormatFuzzyGlobalHeight:HelpHost / Database informationHost:IDID:IconsImage SizeImage size too large.ImagesItalicJustifyKeyboard Shortcuts...Launch actionLimitLimit:LinkLoginLogs (%s)MManage...Model:Move downMove down of one pageMove leftMove rightMove to bottomMove to topMove upMove up of one pageNameNewNextNext RecordNext entryNext tabNoNo action defined.No other language available.No result found.Note (%d/%d)Notes (%s)Notes...OKOpenOpen filtersOpen related recordsOpen reportOpen the calendarOpen the record Open...OptionsPDA ModePNG image (*.png)PastePaste copied textPre-validationPreferencePreferencesPreferences...PreviousPrevious RecordPrevious entryPrevious tabPrintPrint reportPrint...ProfileProfile EditorProfile:QuitRecord saved.Records not removed.Records removed.RelateRelate...Relation EntriesRemove "%s"Remove Remove selected profileRemove selected recordRemove this bookmarkReportReport BugReport...RevisionRevision:SaveSave AsSave As...Save Tree StateSave this recordSave your current versionSearchSearch %sSearch Limit SettingsSearch Limit...Search a record See the modified versionSelectSelect a colorSelect a revisionSelect allSelect parentSelect your actionSelect...Select/Activate current rowSelectionShortcutsShow active recordsShow bookmarks of filtersShow inactive recordsShow plain textShow revisions...Spell CheckingSubject:SwitchSwitch ViewTextText and IconsThe following action requires to close all tabs. Do you want to continue?This record has been modified do you want to save it?This record has been modified while you were editing it.To:TodayToolbarTranslate viewTranslationTrueUnable to set locale %sUndelete selected record UnderlineUnknownUnselect allUse locale formatUser name:Username:ValueView _Logs...WeekWidth:WizardWrite AnywayYYesYou have to select one record.You must select an import file first.You need to save the record before adding translations.Your selection:_Actions..._Add_Auto-Detect_Cancel_Clear_Close Tab_Copy URL_Delete Export_Delete..._Duplicate_E-Mail..._Export Data..._Import Data..._New_Next_Notes..._Previous_Print..._Relate..._Remove_Report..._Save_Save Export_Search_Switch Viewddevelopment modego backgo forwardhlogging everything at INFO levelmnext yearprevious yearsspecify alternate config filespecify the log level: DEBUG, INFO, WARNING, ERROR, CRITICALspecify the login userspecify the server hostname:porttwyProject-Id-Version: PROJECT VERSION Report-Msgid-Bugs-To: EMAIL@ADDRESS POT-Creation-Date: 2025-04-26 17:11+0200 PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE Last-Translator: FULL NAME Language: et Language-Team: et Plural-Forms: nplurals=2; plural=(n != 1); MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: 8bit Generated-By: Babel 2.17.0 %d kirje imporditud.%d kirje salvestatud.%d kirjed imporditud.%d kirjed salvestatud.%s (%s)%s (model name)%s (string)%s%%, .....:Kõik väljadValitud väljadEelnevalt määratletud ekspordidLoo...Otsi...Uus versioon on saadaval!M_anused...ToimingToimingud...LisaLisa kirjele manusLisa kirjele manusLisa olemasolev kirjeLisa välja nimedLisa uus profiilLisa väärtusLisa...Joonda keskeleJoonda vasakuleJoonda paremaleKõik failidEira alati seda hoiatust.Rakenduse vigaRakenduse otseteedOled kindel, et kustutada see kirje?Oled kindel, et kustutada need kirjed?Manus (%s)Manused (%s)Manused...PaksJärjehoidja nimi:Lisa filter järjehoidjatessePoolt:CSV eksport: %sCSV import: %sCSV parameetridÜ_henduTühistaKatkesta ühendus Tryton serverigaTühista salvestamineKontrolli URLi: %sKontrolli versiooniVali keelTühjendaTühjenda väli SulgeSulge vahelehtSulge kõik readSulge ridaVõrdleKonkurentsi erandÜhendu Tryton serverigaKopeeriKopeeri URL lõikelaualeKopeeri _URL...Kopeeri valitud tekstLoo uus kirjeUus kirje Lõika valitud tekstAndmebaas:KuupäevaformaatVaikimisiKustutaKustuta valitud kirjeKustuta Eralaja:Kuvatav formaatKuvatav kuupäevaformaatKuvatud väärtusKas soovid jätkata?AllalaadimineE-kiri...MuudaMuuda kasutaja eelistusiMuuda valitud kirjetMuuda...VigaAva kõik readAva ridaAva/suleVäärLemmikudVälja nimiImporditav fail:OtsiEsiplaani värvVormFormaatHäguneGlobaalneKõrgus:AbiServeri / andmebaasi infoServer:IDIDIkoonidPildi suurusPilt on liiga suur.PildidKursiivÜhtlustaKiirklahvid...Käivita toimingLimiitLimiitLinkLogi sissePalgid (%s)KHalda...Mudel:Liigu allaLiigu lehekülg allaLiigu vasakuleLiigu paremaleLiigu lõppuLiigu algusesseLiigu ülesLiigu lehekülg ülesNimiUusEdasiJärgmine kirjeJärgmineJärgmine vahelehtEiToiming on määramata.Teisi keeli pole saadaval.Tulemusi ei leitud.Märkus (%d%d)Märkused (%s)Märkused...OKAvaAva filtridAva seotud kirjedAva aruanneAva kalenderAva see kirje Ava...ValikudPDA reziimPNG pilt (*.png)AsetaAseta valitud tekstEelkontrollEelistusEelistusedEelistused...EelmineEelmine kirjeEelmineEelmine vahelehtPrindiPrindi aruannePrindi...ProfiilProfiili muutmineProfiil:VäljuKirje salvestatud.Kirjeid ei kustutatud.Kirjed kustutatud.SeoSeosta...Seotud kirjedEemalda "%s"Eemalda Eemalda valitud profiilEemalda valitud kirjeEemalda järjehoidjaAruanneTeatvita veastAruanne...LäbivaatusLäbivaatus:SalvestaSalvesta nimegaSalvesta nimega...Salvesta puu olekSalvesta kirjeSalvesta praegune versioonOtsiOtsi %sOtsingupiirangu seadedOtsingupiirang...Otsi kirjet Vaata muudetud versiooniValiVali värvVali läbivaatusVali kõikVali ülemVali toimingVali...Vali/aktiveeri ridaValikOtseteedNäita aktiivseid kirjeidNäita filtrite järjehoidjaidNäita mitteaktiivseid filtreidNäita tekstinaNäita läbivaatust...Õigekirja kontrollPealkiri:VahetaVaheta vaadetTekstTekst ja ikoonidJärgneva toimingu teostamiseks on vaja sulgeda kõik vahelehed. Kas jätkata?Seda kirjet on muudetud, kas soovid salvestada?Seda kirjet on muudetud.Saaja:TänaTööriistaribaTõlgi vaadeTõlgeTõeneEi saa seadistada lokaati %sTaasta valitud kirje AllajoonitudTundmatuTühista valikKasuta piirkonna formaatiKasutaja nimi:Kasutajanimi:VäärtusVaata _logisidNädalLaius:NõustajaKirjuta ikkagiAJahPead valima vähemasti ühe rea.Esmalt tuleb valida imporditav fail.Kirje tuleb salvestada enne tõlke lisamist.Teie valik:_Toimingud..._Lisa_Automaatne tuvastus_Tühista_Tühjenda_Sulge vaheleht_Kopeeri URL_Kustuta eksport_Kustuta..._Duplikaat_E-kiri..._Ekspordi andmed..._Impordi andmed..._Uus_Edasi_Märkused..._Eelmine_Prindi..._Seosta..._Eemalda_Aruanne..._Salvesta_Salvesta eksport_Otsi_Vaheta vaadetparendusreziimmine tagasimine edasitlogi kõik INFO tasemelmjärgmine aastaeelmine aastasmäära alternatiivne konfiguratsioonifailmäära logimise tase: DEBUG, INFO, WARNING, ERROR, CRITICALmäära kasutaja sisselogimise tunnusmäära server:porttnj././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1703673879.0 tryton-7.0.24/tryton/data/locale/et/LC_MESSAGES/tryton.po0000644000175000017500000004454714543000027021067 0ustar00cedced# Translations template for tryton. # Copyright (C) 2018 Tryton # This file is distributed under the same license as the tryton project. # FIRST AUTHOR , 2018. msgid "" msgstr "Content-Type: text/plain; charset=utf-8\n" msgid "specify alternate config file" msgstr "määra alternatiivne konfiguratsioonifail" msgid "development mode" msgstr "arendusreziim" msgid "logging everything at INFO level" msgstr "logi kõik INFO tasemel" msgid "specify the log level: DEBUG, INFO, WARNING, ERROR, CRITICAL" msgstr "määra logimise tase: DEBUG, INFO, WARNING, ERROR, CRITICAL" msgid "specify the file used to output logging information" msgstr "" msgid "specify the login user" msgstr "määra kasutaja sisselogimise tunnus" msgid "specify the server hostname:port" msgstr "määra server:port" msgid "disable thread usage" msgstr "" #, python-format msgid "Unable to set locale %s" msgstr "Ei saa seadistada lokaati %s" msgid ", " msgstr ", " msgid ",..." msgstr "" #, python-format msgid "%s (%s)" msgstr "%s (%s)" msgid "Select your action" msgstr "Vali toiming" msgid "No action defined." msgstr "Toiming on määramata." msgid "By: " msgstr "Poolt:" msgid "Selection" msgstr "Valik" msgid "Cancel" msgstr "Tühista" msgid "OK" msgstr "OK" msgid "Your selection:" msgstr "Teie valik:" msgid "Save As..." msgstr "Salvesta nimega..." msgid "Do you want to proceed?" msgstr "Kas soovid jätkata?" msgid "Always ignore this warning." msgstr "Eira alati seda hoiatust." msgid "No" msgstr "Ei" msgid "Yes" msgstr "Jah" msgid "Concurrency Exception" msgstr "Konkurentsi erand" msgid "This record has been modified while you were editing it." msgstr "Seda kirjet on muudetud." msgid "Cancel saving" msgstr "Tühista salvestamine" msgid "Compare" msgstr "Võrdle" msgid "See the modified version" msgstr "Vaata muudetud versiooni" msgid "Write Anyway" msgstr "Kirjuta ikkagi" msgid "Save your current version" msgstr "Salvesta praegune versioon" #, fuzzy, python-format msgid "Compare: %s" msgstr "Võrdle: %s" msgid "Close" msgstr "Sulge" msgid "Application Error" msgstr "Rakenduse viga" msgid "Report Bug" msgstr "Teatvita veast" #, python-format msgid "Check URL: %s" msgstr "Kontrolli URLi: %s" #, fuzzy msgid "Unable to check for new version." msgstr "Ei saa kontrollida uut versiooni" msgid "A new version is available!" msgstr "Uus versioon on saadaval!" msgid "Download" msgstr "Allalaadimine" #, fuzzy msgid "Could not get a session." msgstr "Ei saanud serveriga ühendust" msgid "Too many requests. Try again later." msgstr "" #, fuzzy msgid "Not found." msgstr "Tulemusi ei leitud." #, fuzzy msgid "Not Found." msgstr "Tulemusi ei leitud." msgid "..." msgstr "..." msgid "Search..." msgstr "Otsi..." msgid "Create..." msgstr "Loo..." #, fuzzy, python-format msgid "Create \"%s\"..." msgstr "Loo \"%s\"..." msgid "Value" msgstr "Väärtus" msgid "Displayed value" msgstr "Kuvatud väärtus" msgid "Format" msgstr "Formaat" msgid "Display format" msgstr "Kuvatav formaat" msgid "Open the calendar" msgstr "Ava kalender" msgid "Date Format" msgstr "Kuupäevaformaat" msgid "Displayed date format" msgstr "Kuvatav kuupäevaformaat" msgid "y" msgstr "j" msgid "True" msgstr "Tõene" msgid "t" msgstr "t" msgid "False" msgstr "Väär" msgid "Digits" msgstr "" msgid "The number of decimal" msgstr "" #, fuzzy msgid "Template" msgstr "Seo" msgid "Edit..." msgstr "Muuda..." #, fuzzy msgid "View Logs..." msgstr "Vaata _logisid" msgid "Attachments..." msgstr "Manused..." msgid "Notes..." msgstr "Märkused..." msgid "Actions..." msgstr "Toimingud..." msgid "Relate..." msgstr "Seosta..." msgid "Report..." msgstr "Aruanne..." msgid "Print..." msgstr "Prindi..." msgid "E-Mail..." msgstr "E-kiri..." msgid "Bold" msgstr "Paks" msgid "Italic" msgstr "Kursiiv" msgid "Underline" msgstr "Allajoonitud" msgid "Align Left" msgstr "Joonda vasakule" msgid "Align Center" msgstr "Joonda keskele" msgid "Align Right" msgstr "Joonda paremale" msgid "Justify" msgstr "Ühtlusta" msgid "Foreground Color" msgstr "Esiplaani värv" msgid "Select a color" msgstr "Vali värv" msgid "Y" msgstr "A" msgid "M" msgstr "K" msgid "w" msgstr "n" msgid "d" msgstr "p" msgid "h" msgstr "t" msgid "m" msgstr "m" msgid "s" msgstr "s" msgid "Preferences..." msgstr "Eelistused..." msgid "Toolbar" msgstr "Tööriistariba" msgid "Default" msgstr "Vaikimisi" msgid "Text and Icons" msgstr "Tekst ja ikoonid" msgid "Text" msgstr "Tekst" msgid "Icons" msgstr "Ikoonid" msgid "Form" msgstr "Vorm" msgid "Save Column Width" msgstr "" msgid "Save Tree State" msgstr "Salvesta puu olek" msgid "Spell Checking" msgstr "Õigekirja kontroll" msgid "Play Sound for Code Scanner" msgstr "" msgid "PDA Mode" msgstr "PDA reziim" msgid "Search Limit..." msgstr "Otsingupiirang..." msgid "Check Version" msgstr "Kontrolli versiooni" msgid "Options" msgstr "Valikud" #, fuzzy msgid "Documentation..." msgstr "Toimingud..." msgid "Keyboard Shortcuts..." msgstr "Kiirklahvid..." msgid "About..." msgstr "" msgid "Help" msgstr "Abi" msgid "No result found." msgstr "Tulemusi ei leitud." msgid "Favorites" msgstr "Lemmikud" msgid "Manage..." msgstr "Halda..." msgid "Action" msgstr "Toiming" msgid "" "The following action requires to close all tabs.\n" "Do you want to continue?" msgstr "" "Järgneva toimingu teostamiseks on vaja sulgeda kõik vahelehed. \n" "Kas jätkata?" msgid "Application Shortcuts" msgstr "Rakenduse otseteed" msgid "Global" msgstr "Globaalne" msgid "Preferences" msgstr "Eelistused" msgid "Search menu" msgstr "" msgid "Toggle menu" msgstr "" msgid "Previous tab" msgstr "Eelmine vaheleht" msgid "Next tab" msgstr "Järgmine vaheleht" msgid "Shortcuts" msgstr "Otseteed" msgid "Quit" msgstr "Välju" msgid "Edition Shortcuts" msgstr "" msgid "Text Entries" msgstr "" msgid "Cut selected text" msgstr "Lõika valitud tekst" msgid "Copy selected text" msgstr "Kopeeri valitud tekst" msgid "Paste copied text" msgstr "Aseta valitud tekst" msgid "Next entry" msgstr "Järgmine" msgid "Previous entry" msgstr "Eelmine" msgid "Relation Entries" msgstr "Seotud kirjed" msgid "Create new relation" msgstr "" msgid "Open/Search relation" msgstr "" msgid "List Entries" msgstr "" #, fuzzy msgid "Switch view" msgstr "Vaheta vaadet" msgid "Create/Select new line" msgstr "" msgid "Open relation" msgstr "" msgid "Mark line for deletion/removal" msgstr "" msgid "Mark line for removal" msgstr "" msgid "Unmark line for deletion" msgstr "" msgid "List/Tree Shortcuts" msgstr "" msgid "Move Cursor" msgstr "" msgid "Move right" msgstr "Liigu paremale" msgid "Move left" msgstr "Liigu vasakule" msgid "Move up" msgstr "Liigu üles" msgid "Move down" msgstr "Liigu alla" msgid "Move up of one page" msgstr "Liigu lehekülg üles" msgid "Move down of one page" msgstr "Liigu lehekülg alla" msgid "Move to top" msgstr "Liigu algusesse" msgid "Move to bottom" msgstr "Liigu lõppu" msgid "Move to parent" msgstr "" #, fuzzy msgid "Edition" msgstr "Muuda" #, fuzzy msgid "Copy selected rows" msgstr "Kopeeri valitud tekst" msgid "Insert copied rows" msgstr "" msgid "Select all" msgstr "Vali kõik" msgid "Unselect all" msgstr "Tühista valik" msgid "Select parent" msgstr "Vali ülem" msgid "Select/Activate current row" msgstr "Vali/aktiveeri rida" msgid "Toggle selection" msgstr "" msgid "Expand/Collapse" msgstr "Ava/sule" msgid "Expand row" msgstr "Ava rida" msgid "Collapse row" msgstr "Sulge rida" msgid "Toggle row" msgstr "" msgid "Collapse all rows" msgstr "Sulge kõik read" msgid "Expand all rows" msgstr "Ava kõik read" msgid "Close Tab" msgstr "Sulge vaheleht" msgid "modularity, scalability and security" msgstr "" msgid "translator-credits" msgstr "" #, python-format msgid "Attachments (%s)" msgstr "Manused (%s)" msgid "Code Scanner" msgstr "" msgid "Code" msgstr "" msgid "Profile Editor" msgstr "Profiili muutmine" msgid "Profile" msgstr "Profiil" msgid "Add new profile" msgstr "Lisa uus profiil" msgid "Remove selected profile" msgstr "Eemalda valitud profiil" msgid "Host:" msgstr "Server:" msgid "Database:" msgstr "Andmebaas:" msgid "Fetching databases list" msgstr "" msgid "Username:" msgstr "Kasutajanimi:" msgid "Incompatible version of the server." msgstr "" #, fuzzy msgid "Could not connect to the server." msgstr "Ei saanud serveriga ühendust" msgid "Login" msgstr "Logi sisse" msgid "_Cancel" msgstr "_Tühista" msgid "Cancel connection to the Tryton server" msgstr "Katkesta ühendus Tryton serveriga" msgid "C_onnect" msgstr "Ü_hendu" msgid "Connect the Tryton server" msgstr "Ühendu Tryton serveriga" msgid "Profile:" msgstr "Profiil:" msgid "Host / Database information" msgstr "Serveri / andmebaasi info" msgid "User name:" msgstr "Kasutaja nimi:" #, fuzzy msgid "Unable to complete email entry" msgstr "Ei saa kustutada nõustajat %s" #, fuzzy, python-format msgid "E-mail %s" msgstr "E-kiri %s" msgid "To:" msgstr "Saaja:" #, fuzzy msgid "Cc:" msgstr "CC:" msgid "Bcc:" msgstr "" msgid "Subject:" msgstr "Pealkiri:" #, fuzzy msgid "Body" msgstr "Sisu:" #, fuzzy msgid "Reports" msgstr "Aruanne" #, fuzzy msgid "Attachments" msgstr "Manus:" msgid "Files" msgstr "" msgid "Send" msgstr "" #, fuzzy msgid "Select File" msgstr "Vali kõik" #, fuzzy msgid "Remove File" msgstr "Eemalda " msgid "Select" msgstr "Vali" msgid "Add..." msgstr "Lisa..." #, fuzzy msgid "Preview" msgstr "Eelmine" msgid "Previous" msgstr "Eelmine" msgid "Next" msgstr "Edasi" #, python-format msgid "Attachment (%s)" msgstr "Manus (%s)" #, python-format msgid "Note (%d/%d)" msgstr "Märkus (%d%d)" msgid "You have to select one record." msgstr "Pead valima vähemasti ühe rea." msgid "Are you sure to remove this record?" msgstr "Oled kindel, et kustutada see kirje?" msgid "Are you sure to remove those records?" msgstr "Oled kindel, et kustutada need kirjed?" msgid "Records not removed." msgstr "Kirjeid ei kustutatud." msgid "Records removed." msgstr "Kirjed kustutatud." msgid "Working now on the duplicated record(s)." msgstr "" msgid "Record saved." msgstr "Kirje salvestatud." msgid "" "This record has been modified\n" "do you want to save it?" msgstr "" "Seda kirjet on muudetud,\n" "kas soovid salvestada?" msgid "Launch action" msgstr "Käivita toiming" msgid "Relate" msgstr "Seo" msgid "Open related records" msgstr "Ava seotud kirjed" msgid "Report" msgstr "Aruanne" msgid "Open report" msgstr "Ava aruanne" msgid "Print" msgstr "Prindi" msgid "Print report" msgstr "Prindi aruanne" msgid "_Copy URL" msgstr "_Kopeeri URL" msgid "Copy URL into clipboard" msgstr "Kopeeri URL lõikelauale" msgid "Unknown" msgstr "Tundmatu" msgid "Limit" msgstr "Limiit" msgid "Search Limit Settings" msgstr "Otsingupiirangu seaded" msgid "Limit:" msgstr "Limiit" #, python-format msgid "Logs (%s)" msgstr "Palgid (%s)" msgid "Model:" msgstr "Mudel:" msgid "ID:" msgstr "ID" #, fuzzy msgid "Created by:" msgstr "Loomise kuupäev" #, fuzzy msgid "Created at:" msgstr "Loomise kuupäev" #, fuzzy msgid "Last Modified by:" msgstr "Muutja:" #, fuzzy msgid "Last Modified at:" msgstr "Muutmise kuupäev:" #, python-format msgid "Notes (%s)" msgstr "Märkused (%s)" msgid "Edit User Preferences" msgstr "Muuda kasutaja eelistusi" msgid "Preference" msgstr "Eelistus" msgid "Revision" msgstr "Läbivaatus" msgid "Select a revision" msgstr "Vali läbivaatus" msgid "Revision:" msgstr "Läbivaatus:" msgid "_Switch View" msgstr "_Vaheta vaadet" msgid "Switch View" msgstr "Vaheta vaadet" msgid "_Previous" msgstr "_Eelmine" msgid "Previous Record" msgstr "Eelmine kirje" msgid "_Next" msgstr "_Edasi" msgid "Next Record" msgstr "Järgmine kirje" msgid "_Search" msgstr "_Otsi" msgid "_New" msgstr "_Uus" msgid "Create a new record" msgstr "Loo uus kirje" msgid "_Save" msgstr "_Salvesta" msgid "Save this record" msgstr "Salvesta kirje" msgid "_Reload/Undo" msgstr "" msgid "Reload/Undo" msgstr "" msgid "_Duplicate" msgstr "_Duplikaat" msgid "_Delete..." msgstr "_Kustuta..." msgid "View _Logs..." msgstr "Vaata _logisid" msgid "Show revisions..." msgstr "Näita läbivaatust..." msgid "A_ttachments..." msgstr "M_anused..." msgid "Add an attachment to the record" msgstr "Lisa kirjele manus" msgid "_Notes..." msgstr "_Märkused..." msgid "Add a note to the record" msgstr "Lisa kirjele manus" msgid "_Actions..." msgstr "_Toimingud..." msgid "_Relate..." msgstr "_Seosta..." msgid "_Report..." msgstr "_Aruanne..." msgid "_Print..." msgstr "_Prindi..." msgid "_E-Mail..." msgstr "_E-kiri..." #, fuzzy msgid "Send an e-mail using the record" msgstr "Lisa kirjele manus" msgid "_Export Data..." msgstr "_Ekspordi andmed..." msgid "_Import Data..." msgstr "_Impordi andmed..." msgid "Copy _URL..." msgstr "Kopeeri _URL..." msgid "_Close Tab" msgstr "_Sulge vaheleht" msgid "All fields" msgstr "Kõik väljad" msgid "_Add" msgstr "_Lisa" msgid "_Remove" msgstr "_Eemalda" msgid "_Clear" msgstr "_Tühjenda" msgid "Fields selected" msgstr "Valitud väljad" msgid "CSV Parameters" msgstr "CSV parameetrid" msgid "Delimiter:" msgstr "Eralaja:" msgid "Quote char:" msgstr "" msgid "Encoding:" msgstr "" msgid "Use locale format" msgstr "Kasuta piirkonna formaati" msgid "Field name" msgstr "Välja nimi" #, python-format msgid "CSV Export: %s" msgstr "CSV eksport: %s" msgid "_Save Export" msgstr "_Salvesta eksport" #, fuzzy msgid "_URL Export" msgstr "_Salvesta eksport" msgid "_Delete Export" msgstr "_Kustuta eksport" msgid "Predefined exports" msgstr "Eelnevalt määratletud ekspordid" msgid "Name" msgstr "Nimi" msgid "Open" msgstr "Ava" msgid "Save" msgstr "Salvesta" #, fuzzy msgid "Listed Records" msgstr "Muuda valitud kirjet" #, fuzzy msgid "Selected Records" msgstr "Muuda valitud kirjet" msgid "Ignore search limit" msgstr "" msgid "Add field names" msgstr "Lisa välja nimed" #, python-format msgid "%s (string)" msgstr "%s (string)" #, python-format msgid "%s (model name)" msgstr "%s (model name)" #, fuzzy, python-format msgid "%s/Record Name" msgstr "%s (record name)" msgid "What is the name of this export?" msgstr "" #, python-format msgid "Override '%s' definition?" msgstr "" #, python-format msgid "%d record saved." msgstr "%d kirje salvestatud." #, python-format msgid "%d records saved." msgstr "%d kirjed salvestatud." msgid "Export failed" msgstr "" msgid "Link" msgstr "Link" msgid "Delete" msgstr "Kustuta" msgid "Discard changes" msgstr "" msgid "Save and New" msgstr "" #, fuzzy msgid "Add and New" msgstr "Lisa väärtus" msgid "Add" msgstr "Lisa" msgid "Apply changes" msgstr "" msgid "Switch" msgstr "Vaheta" msgid "Remove " msgstr "Eemalda " msgid "Create a new record " msgstr "Uus kirje " msgid "Delete selected record " msgstr "Kustuta " msgid "Undelete selected record " msgstr "Taasta valitud kirje " #, python-format msgid "CSV Import: %s" msgstr "CSV import: %s" msgid "_Auto-Detect" msgstr "_Automaatne tuvastus" msgid "File to Import:" msgstr "Imporditav fail:" msgid "Open..." msgstr "Ava..." msgid "Lines to Skip:" msgstr "" msgid "You must select an import file first." msgstr "Esmalt tuleb valida imporditav fail." msgid "Detection failed" msgstr "" #, python-format msgid "Unknown column header \"%s\"" msgstr "" msgid "Error" msgstr "Viga" msgid "Import failed" msgstr "" #, python-format msgid "%d record imported." msgstr "%d kirje imporditud." #, python-format msgid "%d records imported." msgstr "%d kirjed imporditud." msgid "Search" msgstr "Otsi" msgid "New" msgstr "Uus" #, python-format msgid "Search %s" msgstr "Otsi %s" msgid "Wizard" msgstr "Nõustaja" msgid "ID" msgstr "ID" #, fuzzy msgid "Created by" msgstr "Loomise kuupäev" #, fuzzy msgid "Created at" msgstr "Loomise kuupäev" #, fuzzy msgid "Edited by" msgstr "Muuda" #, fuzzy msgid "Edited at" msgstr "Muuda" msgid "Unable to set view tree state" msgstr "" #, fuzzy, python-format msgid "\"%s\" is not valid according to its domain." msgstr "\"%s\" ei ole selle domeeni jaoks lubatud" #, fuzzy, python-format msgid "\"%s\" is required." msgstr "\"%s\" on nõutud" #, fuzzy, python-format msgid "The values of \"%s\" are not valid." msgstr "\"%s\" väärtused ei ole lubatud" msgid "Pre-validation" msgstr "Eelkontroll" msgid ":" msgstr ":" msgid "Scan" msgstr "" msgid "Image Size" msgstr "Pildi suurus" msgid "Width:" msgstr "Laius:" msgid "Height:" msgstr "Kõrgus:" msgid "PNG image (*.png)" msgstr "PNG pilt (*.png)" msgid "Save As" msgstr "Salvesta nimega" msgid "Image size too large." msgstr "Pilt on liiga suur." msgid "Copy" msgstr "Kopeeri" msgid "Paste" msgstr "Aseta" msgid ".." msgstr ".." msgid "Open filters" msgstr "Ava filtrid" msgid "Show bookmarks of filters" msgstr "Näita filtrite järjehoidjaid" msgid "Remove this bookmark" msgstr "Eemalda järjehoidja" msgid "Bookmark this filter" msgstr "Lisa filter järjehoidjatesse" msgid "Show active records" msgstr "Näita aktiivseid kirjeid" msgid "Show inactive records" msgstr "Näita mitteaktiivseid filtreid" msgid "Bookmark Name:" msgstr "Järjehoidja nimi:" msgid "Find" msgstr "Otsi" msgid "Today" msgstr "Täna" msgid "go back" msgstr "mine tagasi" msgid "go forward" msgstr "mine edasi" msgid "previous year" msgstr "eelmine aasta" msgid "next year" msgstr "järgmine aasta" msgid "Day" msgstr "" msgid "Week" msgstr "Nädal" #, fuzzy msgid "Month" msgstr "Kuu vaade" msgid "Select..." msgstr "Vali..." msgid "Clear" msgstr "Tühjenda" msgid "All files" msgstr "Kõik failid" msgid "Show plain text" msgstr "Näita tekstina" msgid "Add value" msgstr "Lisa väärtus" #, python-format msgid "Remove \"%s\"" msgstr "Eemalda \"%s\"" msgid "Images" msgstr "Pildid" msgid "Add existing record" msgstr "Lisa olemasolev kirje" msgid "Remove selected record" msgstr "Eemalda valitud kirje" msgid "Open the record " msgstr "Ava see kirje " msgid "Clear the field " msgstr "Tühjenda väli " msgid "Search a record " msgstr "Otsi kirjet " msgid "Edit selected record" msgstr "Muuda valitud kirjet" msgid "Delete selected record" msgstr "Kustuta valitud kirje" #, python-format msgid "%s%%" msgstr "%s%%" msgid "Choose a language" msgstr "Vali keel" msgid "Translation" msgstr "Tõlge" msgid "Edit" msgstr "Muuda" msgid "Fuzzy" msgstr "Hägune" msgid "You need to save the record before adding translations." msgstr "Kirje tuleb salvestada enne tõlke lisamist." msgid "No other language available." msgstr "Teisi keeli pole saadaval." msgid "#ERROR" msgstr "" msgid "Translate view" msgstr "Tõlgi vaade" ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1745680285.1241102 tryton-7.0.24/tryton/data/locale/fa/0000755000175000017500000000000015003173635015353 5ustar00cedced././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1745680285.1641107 tryton-7.0.24/tryton/data/locale/fa/LC_MESSAGES/0000755000175000017500000000000015003173635017140 5ustar00cedced././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1745680279.0 tryton-7.0.24/tryton/data/locale/fa/LC_MESSAGES/tryton.mo0000644000175000017500000004044515003173627021044 0ustar00cedced", <=Qbw #?OX _jn  #%8^o~&   '5;Q Wa s  )= O Yemt    ! )39 ITd jt  " 8FLSbg mw y    ,I Zenq v  !3E T _kz     % 1 =Jby     !7G\u|     '=M_nw ~I58W[ al}   ( 3= CQ Vw~( %7 > N Z _ l t {      ! !!! )!4! :!G! O!\!^!o! w!! !!$! ! !!!<"A" X"y"{""""$ :$'[$)$ $$ $$$$$$$%%3(%\%q%0%%% %% %9&5:&$p&"&&&&8&.'HD'M'''(!( =(I(d(( ( (,((()")2)P)Y)k)) ))) )$)*0* M**n***** ++(+>+Q+.e+%+.+ + + , ,-,M,],o,v,,,,,9,-#'- K-V-]-f- u- -3- - - ---9 . G.'T.|. ..". .. .//'///H/&a/////&/00!0*0@0!G04i0 000001-#1Q1k11 11(11"2 22?2#Q2u22222222 3 33:3J3S3p3$3"3 3 3$3 4 4'-4"U4x4 44 444 445.5M51l5 5 5$55#5,6 F6S6#l6666606 7''70O7-77$77 8 8$8:8A8o[8m8g999 99999 ::0:@L:0::5:;";5; J;U;u;2~;; ;_;&<><@<=G<J<f<7=L= _=m= ===== = == >+> J> T>^> v> >>%>>> >> > ?!?#? ;? H?V?;X??:? ? ????j/@.@4@@ A!A#A%d record imported.%d record saved.%d records imported.%d records saved.%s (%s)%s (model name)%s (string)%s%%, .....:All fieldsFields selectedPredefined exportsCreate...Search...A new version is available!A_ttachments...About...ActionActions...AddAdd a note to the recordAdd an attachment to the recordAdd existing recordAdd new profileAdd valueAdd...All filesAlways ignore this warning.Application ErrorAre you sure to remove this record?Are you sure to remove those records?Attachments (%s)Attachments...Bookmark Name:Bookmark this filterBy: CSV Export: %sCSV Import: %sCSV ParametersC_onnectCancelCancel connection to the Tryton serverCancel savingCheck URL: %sCheck VersionClearClear the field CloseClose TabCollapse all rowsCollapse rowCompareCompare: %sConcurrency ExceptionConnect the Tryton serverCopy URL into clipboardCopy _URL...Copy selected textCreate a new recordCreate a new record Create new relationCut selected textDatabase:Date FormatDefaultDeleteDelete selected record Delimiter:Display formatDisplayed date formatDisplayed valueDo you want to proceed?DownloadE-Mail...E-mail %sEditEdit User PreferencesEdit...Encoding:ErrorExpand all rowsExpand rowExpand/CollapseFalseFavoritesFetching databases listField nameFile to Import:FindFormFormatFuzzyHeight:HelpHost / Database informationHost:IDID:IconsImage SizeImage size too large.ImagesKeyboard Shortcuts...Launch actionLimitLimit:Lines to Skip:LinkLoginLogs (%s)MManage...Model:Move CursorMove downMove down of one pageMove to bottomMove to parentMove to topMove upMove up of one pageNameNewNextNext RecordNoNo action defined.No other language available.No result found.Notes (%s)Notes...OKOpenOpen filtersOpen related recordsOpen relationOpen reportOpen the calendarOpen the record Open...Open/Search relationOptionsOverride '%s' definition?PDA ModePNG image (*.png)Paste copied textPre-validationPreferencePreferencesPreferences...PreviousPrevious RecordPrintPrint reportPrint...ProfileProfile EditorProfile:QuitQuote char:Record saved.Records not removed.Records removed.RelateRelate...Reload/UndoRemove "%s"Remove Remove selected profileRemove selected recordRemove this bookmarkReportReport BugReport...RevisionRevision:SaveSave AsSave As...Save Tree StateSave this recordSave your current versionSearchSearch %sSearch Limit SettingsSearch Limit...Search a record See the modified versionSelectSelect a colorSelect a revisionSelect allSelect parentSelect your actionSelect...Select/Activate current rowSelectionShow active recordsShow bookmarks of filtersShow inactive recordsShow plain textShow revisions...Spell CheckingSubject:SwitchSwitch ViewTextText and IconsThe following action requires to close all tabs. Do you want to continue?This record has been modified do you want to save it?This record has been modified while you were editing it.To:TodayToggle rowToggle selectionToolbarTranslate viewTranslationTrueUnable to set locale %sUnable to set view tree stateUndelete selected record UnknownUnmark line for deletionUnselect allUser name:Username:ValueView _Logs...WeekWhat is the name of this export?Width:WizardWorking now on the duplicated record(s).Write AnywayYYesYou have to select one record.You must select an import file first.You need to save the record before adding translations.Your selection:_Actions..._Add_Auto-Detect_Cancel_Clear_Close Tab_Copy URL_Delete Export_Delete..._Duplicate_E-Mail..._Export Data..._Import Data..._New_Next_Notes..._Previous_Print..._Relate..._Reload/Undo_Remove_Report..._Save_Save Export_Search_Switch Viewddevelopment modego backgo forwardhlogging everything at INFO levelmmodularity, scalability and securitynext yearprevious yearsspecify alternate config filespecify the log level: DEBUG, INFO, WARNING, ERROR, CRITICALspecify the login userspecify the server hostname:portttranslator-creditswyProject-Id-Version: PROJECT VERSION Report-Msgid-Bugs-To: EMAIL@ADDRESS POT-Creation-Date: 2025-04-26 17:11+0200 PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE Last-Translator: FULL NAME Language: fa Language-Team: fa Plural-Forms: nplurals=1; plural=0; MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: 8bit Generated-By: Babel 2.17.0 %d پرونده وارد شد.%d پرونده ذخیره شد.%d پرونده ها وارد شدند.%d پرونده های ذخیره شده."%s" (%s)نام مدل %sمتنی %s%s%%، .....:همه فیلد هافیلد های انتخابیاستخراج از پیش تعریف شدهایجاد...جستجو...نسخه جدید در دسترس می باشد!_پیوست...درباره...اقداماقدامات...افزودنیک یادداشت به پرونده اضافه کنیدپیوست را به پرونده اضافه کنیدافزودن پرونده موجودافزودن پرفایل جدیدافزودن مقدارافزودن...همه فایل هاهمیشه این هشدار را نادیده بگیر.خطای برنامهبرای حذف این پرونده اطمینان کامل دارید؟برای حذف این پرونده ها اطمینان کامل دارید؟پیوستها (%s)پیوست ها...نام بوک مارک :بوک مارک این فیلترتوسط : صادرکردن(CSV) : %sوارد کردن (CSV) : %sمولفه های CSV_ارتباطانصرافلغو اتصال به سرور تریتونلغو ذخیره کردنبررسی URL : %sبررسی نسخهپاک کردنپاکسازی فیلد بستنبستن برگهبستن همه ردیف هابستن ردیفمقایسهمقایسه: %sهمزمانی استثنااتصال سرور تریتونکپی URL درون کلیپبورد_رونوشت URL...کپی متن انتخابیایجاد پرونده جدیدایجاد یک پرونده جدید ایجاد رابطه جدیدبرش متن انتخابیپایگاه داده:فرمت تاریخپیش فرضحذفحذف پرونده انتخابی جدا کننده:فرمت نمایشفرمت تاریخ تمایش داده شدهمقدار نمایش داده شدهآیا می خواهید پردازش شود؟دانلودایمیل...ایمیل %sویرایشویرایش ظاهر برنامه کاربرویرایش...رمزگذاری:خطاگشودن همه ردیف هاگسترش ردیفگشودن - بستنغلطعلاقمندی هاوارد کردن فهرست های پایگاه دادهنام فیلدفایل برای واردکردن:یافتنفرمقالبنامانوسارتفاع:راهنمااطلاعات میزبان / پایگاه دادههاست:شناسهشناسه:آیکون هااندازه تصویراندازه تصویر بیش از حد بزرگ است.تصاویرمیانبرهای صفحه کلید...انجام اقداممحدودمحدودیت:چشم پوشی از سطرهای:پیوندورود به سیستمسیاهههای مربوط (%s)Mمدیریت...مدل:حرکت مکان نماحرکت به پایینحرکت به پایین یک صفحهحرکت به انتهاانتقال به منبعحرکت به بالاحرکت به بالاحرکت به بالای یک صفحهنامجدیدبعدیپرونده بعدیخیراقدامی تعریف نشده.هیچ زبان دیگری در دسترس نیست.نتیجه ای پیدا نشد.یادداشت ها(%s)یادداشت ها...بلیباز کردنبازکردن فیلترهابازکردن پرونده های مرتبطبازکردن رابطهباز کردن گزارشبازکردن تقویمبازکردن پرونده باز کردن...بازکردن / ارتباط جستجوگزینه‌هابازنویسی %s تعاریف؟حالت PDAPNG image (*.png)چسباندن متن کپی شدهپیش-اعتبارسنجیظاهرظاهر برنامهظاهر برنامه...قبلیپرونده قبلیچاپچاپ گزارشچاپ...پروفایلویرایشگر پروفایلپروفایل:خروجکاراکترنقل قول:پرونده ذخیره شد.پرونده ها حذف نشدند.پرونده ها حذف شدند.مرتبطمرتبط...بارگیری مجدد / واگردحذف "%s"حذف حذف پرفایل انتخاب شدهحذف پرونده انتخابیحذف این بوک مارکگزارشگزارش اشکال(باگ)گزارش...تجدید نظرتجدید نظر:ذخیرهذخیره با نامذخیره با نام...ذخیره حالت درختیذخیره این پروندهنسخه فعلی خود را ذخیره کنیدجستجوجستجو %sتنظیمات جستجو محدودجستجو محدود...جستجوی یک پرونده نسخه اصلاح شده را ببینیدانتخابانتخاب یک رنگانتخاب یک تجدید نظرانتخاب همهانتخاب منبعانتخاب اقدام شماانتخاب...انتخاب/فعال کردن ردیف جاریانتخابنمایش پرونده های فعالنمایش بوک مارک های فیلترهانمایش پرونده های غیرفعالنمایش متن سادهمشاهده تجدیدنظرها...غلط ‌یاب املاییموضوع :تعویضتعویض نمایهمتنمتن و آیکون هااقدام زیر نیاز به بستن همه برگه ها دارد. می خواهید ادامه دهید؟این پرونده هنوز اصلاح نشده است. می‌خواهید آن را ذخیره کنید ؟این پرونده در حالی که شما در حال ویرایش آن بوید، اصلاح شد.به:امروززنجیر ردیفانتخاب زنجیرنوارابزارنمایش ترجمهترجمهدرستقادر به تنظیم محلی: "%s" نیستقادر به تنظیم حالت نمایش درختی نیستبازگردانی فایل انتخابی نامعلومبرای حذف علامت سطر را برداریدانتخاب نکردن همهنام کاربر:نام کاربری:مقدار_مشاهده گزارشات...هفتهنام این فایل استخراجی چیست؟عرض:دستیارهم اکنون کار بر روی پرونده(ها) تکثیر شده انجام می شود.نوشتن بهرحالYبلهشما باید یک پرونده را انتخاب کنید.شما باید ابتدا فایل ورودی را انتخاب کنید.قبل از اضافه کردن ترجمه ها شما باید پرونده را ذخیره کنید.انتخاب شما:_اقدامات..._افزودن_شناسایی - خودکار_انصراف_پاکسازی_بستن برگه_رونوشت URL_حذف استخراج_حذف..._تکثیر_ایمیل..._صادرات داده ها..._واردات داده ها..._جدید_بعدی_یادداشت ها..._قبلی_چاپ..._مرتبط..._بارگیری مجدد / واگرد_حذف_گزارش..._ذخیره_ذخیره استخراج_جستجو_تعویض نمایهdحالت پیشرفتهبازگشتبرو جلوhگزارشگیری همه چیز در سطح اطلاعاتmمدولاربودن، مقیاس پذیری و امنیتسال بعدسال قبلsفایل پیکربندی جایگزین را مشخص کنیدتعیین سطح گزارش : اشکال زدایی، اطلاعات، هشدار، خطا، بحرانیتعیین ورود به سیستم کاربرتعیین نام میزبان سرور : درگاهtپرویز همایون نژادwy././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1703673879.0 tryton-7.0.24/tryton/data/locale/fa/LC_MESSAGES/tryton.po0000644000175000017500000005424414543000027021040 0ustar00cedced# Translations template for tryton. # Copyright (C) 2018 Tryton # This file is distributed under the same license as the tryton project. # FIRST AUTHOR , 2018. msgid "" msgstr "Content-Type: text/plain; charset=utf-8\n" msgid "specify alternate config file" msgstr "فایل پیکربندی جایگزین را مشخص کنید" msgid "development mode" msgstr "حالت پیشرفته" msgid "logging everything at INFO level" msgstr "گزارشگیری همه چیز در سطح اطلاعات" msgid "specify the log level: DEBUG, INFO, WARNING, ERROR, CRITICAL" msgstr "تعیین سطح گزارش : اشکال زدایی، اطلاعات، هشدار، خطا، بحرانی" msgid "specify the file used to output logging information" msgstr "" msgid "specify the login user" msgstr "تعیین ورود به سیستم کاربر" msgid "specify the server hostname:port" msgstr "تعیین نام میزبان سرور : درگاه" msgid "disable thread usage" msgstr "" #, python-format msgid "Unable to set locale %s" msgstr "قادر به تنظیم محلی: \"%s\" نیست" msgid ", " msgstr "، " msgid ",..." msgstr "" #, python-format msgid "%s (%s)" msgstr "\"%s\" (%s)" msgid "Select your action" msgstr "انتخاب اقدام شما" msgid "No action defined." msgstr "اقدامی تعریف نشده." msgid "By: " msgstr "توسط : " msgid "Selection" msgstr "انتخاب" msgid "Cancel" msgstr "انصراف" msgid "OK" msgstr "بلی" msgid "Your selection:" msgstr "انتخاب شما:" msgid "Save As..." msgstr "ذخیره با نام..." msgid "Do you want to proceed?" msgstr "آیا می خواهید پردازش شود؟" msgid "Always ignore this warning." msgstr "همیشه این هشدار را نادیده بگیر." msgid "No" msgstr "خیر" msgid "Yes" msgstr "بله" msgid "Concurrency Exception" msgstr "همزمانی استثنا" msgid "This record has been modified while you were editing it." msgstr "این پرونده در حالی که شما در حال ویرایش آن بوید، اصلاح شد." msgid "Cancel saving" msgstr "لغو ذخیره کردن" msgid "Compare" msgstr "مقایسه" msgid "See the modified version" msgstr "نسخه اصلاح شده را ببینید" msgid "Write Anyway" msgstr "نوشتن بهرحال" msgid "Save your current version" msgstr "نسخه فعلی خود را ذخیره کنید" #, python-format msgid "Compare: %s" msgstr "مقایسه: %s" msgid "Close" msgstr "بستن" msgid "Application Error" msgstr "خطای برنامه" msgid "Report Bug" msgstr "گزارش اشکال(باگ)" #, python-format msgid "Check URL: %s" msgstr "بررسی URL : %s" #, fuzzy msgid "Unable to check for new version." msgstr "قادر به بررسی نسخه جدید نیست" msgid "A new version is available!" msgstr "نسخه جدید در دسترس می باشد!" msgid "Download" msgstr "دانلود" #, fuzzy msgid "Could not get a session." msgstr "نمی تواند به سرور متصل شود" msgid "Too many requests. Try again later." msgstr "" #, fuzzy msgid "Not found." msgstr "نتیجه ای پیدا نشد." #, fuzzy msgid "Not Found." msgstr "نتیجه ای پیدا نشد." msgid "..." msgstr "..." msgid "Search..." msgstr "جستجو..." msgid "Create..." msgstr "ایجاد..." #, fuzzy, python-format msgid "Create \"%s\"..." msgstr "ایجاد "%s"..." msgid "Value" msgstr "مقدار" msgid "Displayed value" msgstr "مقدار نمایش داده شده" msgid "Format" msgstr "قالب" msgid "Display format" msgstr "فرمت نمایش" msgid "Open the calendar" msgstr "بازکردن تقویم" msgid "Date Format" msgstr "فرمت تاریخ" msgid "Displayed date format" msgstr "فرمت تاریخ تمایش داده شده" msgid "y" msgstr "y" msgid "True" msgstr "درست" msgid "t" msgstr "t" msgid "False" msgstr "غلط" msgid "Digits" msgstr "" msgid "The number of decimal" msgstr "" #, fuzzy msgid "Template" msgstr "مرتبط" msgid "Edit..." msgstr "ویرایش..." #, fuzzy msgid "View Logs..." msgstr "_مشاهده گزارشات..." msgid "Attachments..." msgstr "پیوست ها..." msgid "Notes..." msgstr "یادداشت ها..." msgid "Actions..." msgstr "اقدامات..." msgid "Relate..." msgstr "مرتبط..." msgid "Report..." msgstr "گزارش..." msgid "Print..." msgstr "چاپ..." msgid "E-Mail..." msgstr "ایمیل..." msgid "Bold" msgstr "" msgid "Italic" msgstr "" msgid "Underline" msgstr "" msgid "Align Left" msgstr "" msgid "Align Center" msgstr "" msgid "Align Right" msgstr "" msgid "Justify" msgstr "" msgid "Foreground Color" msgstr "" msgid "Select a color" msgstr "انتخاب یک رنگ" msgid "Y" msgstr "Y" msgid "M" msgstr "M" msgid "w" msgstr "w" msgid "d" msgstr "d" msgid "h" msgstr "h" msgid "m" msgstr "m" msgid "s" msgstr "s" msgid "Preferences..." msgstr "ظاهر برنامه..." msgid "Toolbar" msgstr "نوارابزار" msgid "Default" msgstr "پیش فرض" msgid "Text and Icons" msgstr "متن و آیکون ها" msgid "Text" msgstr "متن" msgid "Icons" msgstr "آیکون ها" msgid "Form" msgstr "فرم" msgid "Save Column Width" msgstr "" msgid "Save Tree State" msgstr "ذخیره حالت درختی" msgid "Spell Checking" msgstr "غلط ‌یاب املایی" msgid "Play Sound for Code Scanner" msgstr "" msgid "PDA Mode" msgstr "حالت PDA" msgid "Search Limit..." msgstr "جستجو محدود..." msgid "Check Version" msgstr "بررسی نسخه" msgid "Options" msgstr "گزینه‌ها" #, fuzzy msgid "Documentation..." msgstr "اقدامات..." msgid "Keyboard Shortcuts..." msgstr "میانبرهای صفحه کلید..." msgid "About..." msgstr "درباره..." msgid "Help" msgstr "راهنما" msgid "No result found." msgstr "نتیجه ای پیدا نشد." msgid "Favorites" msgstr "علاقمندی ها" msgid "Manage..." msgstr "مدیریت..." msgid "Action" msgstr "اقدام" msgid "" "The following action requires to close all tabs.\n" "Do you want to continue?" msgstr "" "اقدام زیر نیاز به بستن همه برگه ها دارد.\n" "می خواهید ادامه دهید؟" msgid "Application Shortcuts" msgstr "" msgid "Global" msgstr "" msgid "Preferences" msgstr "ظاهر برنامه" msgid "Search menu" msgstr "" msgid "Toggle menu" msgstr "" #, fuzzy msgid "Previous tab" msgstr "_برگه قبلی" #, fuzzy msgid "Next tab" msgstr "_برگه بعدی" msgid "Shortcuts" msgstr "" msgid "Quit" msgstr "خروج" msgid "Edition Shortcuts" msgstr "" msgid "Text Entries" msgstr "" msgid "Cut selected text" msgstr "برش متن انتخابی" msgid "Copy selected text" msgstr "کپی متن انتخابی" msgid "Paste copied text" msgstr "چسباندن متن کپی شده" msgid "Next entry" msgstr "" msgid "Previous entry" msgstr "" msgid "Relation Entries" msgstr "" msgid "Create new relation" msgstr "ایجاد رابطه جدید" msgid "Open/Search relation" msgstr "بازکردن / ارتباط جستجو" msgid "List Entries" msgstr "" #, fuzzy msgid "Switch view" msgstr "تعویض نمایه" msgid "Create/Select new line" msgstr "" msgid "Open relation" msgstr "بازکردن رابطه" msgid "Mark line for deletion/removal" msgstr "" msgid "Mark line for removal" msgstr "" msgid "Unmark line for deletion" msgstr "برای حذف علامت سطر را بردارید" msgid "List/Tree Shortcuts" msgstr "" msgid "Move Cursor" msgstr "حرکت مکان نما" #, fuzzy msgid "Move right" msgstr "حرکت به راست" #, fuzzy msgid "Move left" msgstr "حرکت به چپ" msgid "Move up" msgstr "حرکت به بالا" msgid "Move down" msgstr "حرکت به پایین" msgid "Move up of one page" msgstr "حرکت به بالای یک صفحه" msgid "Move down of one page" msgstr "حرکت به پایین یک صفحه" msgid "Move to top" msgstr "حرکت به بالا" msgid "Move to bottom" msgstr "حرکت به انتها" msgid "Move to parent" msgstr "انتقال به منبع" #, fuzzy msgid "Edition" msgstr "ویرایش" #, fuzzy msgid "Copy selected rows" msgstr "کپی متن انتخابی" msgid "Insert copied rows" msgstr "" msgid "Select all" msgstr "انتخاب همه" msgid "Unselect all" msgstr "انتخاب نکردن همه" msgid "Select parent" msgstr "انتخاب منبع" msgid "Select/Activate current row" msgstr "انتخاب/فعال کردن ردیف جاری" msgid "Toggle selection" msgstr "انتخاب زنجیر" msgid "Expand/Collapse" msgstr "گشودن - بستن" msgid "Expand row" msgstr "گسترش ردیف" msgid "Collapse row" msgstr "بستن ردیف" msgid "Toggle row" msgstr "زنجیر ردیف" msgid "Collapse all rows" msgstr "بستن همه ردیف ها" msgid "Expand all rows" msgstr "گشودن همه ردیف ها" msgid "Close Tab" msgstr "بستن برگه" msgid "modularity, scalability and security" msgstr "مدولاربودن، مقیاس پذیری و امنیت" msgid "translator-credits" msgstr "پرویز همایون نژاد" #, python-format msgid "Attachments (%s)" msgstr "پیوستها (%s)" msgid "Code Scanner" msgstr "" msgid "Code" msgstr "" msgid "Profile Editor" msgstr "ویرایشگر پروفایل" msgid "Profile" msgstr "پروفایل" msgid "Add new profile" msgstr "افزودن پرفایل جدید" msgid "Remove selected profile" msgstr "حذف پرفایل انتخاب شده" msgid "Host:" msgstr "هاست:" msgid "Database:" msgstr "پایگاه داده:" msgid "Fetching databases list" msgstr "وارد کردن فهرست های پایگاه داده" msgid "Username:" msgstr "نام کاربری:" #, fuzzy msgid "Incompatible version of the server." msgstr "نسخه ناسازگار سرور" #, fuzzy msgid "Could not connect to the server." msgstr "نمی تواند به سرور متصل شود" msgid "Login" msgstr "ورود به سیستم" msgid "_Cancel" msgstr "_انصراف" msgid "Cancel connection to the Tryton server" msgstr "لغو اتصال به سرور تریتون" msgid "C_onnect" msgstr "_ارتباط" msgid "Connect the Tryton server" msgstr "اتصال سرور تریتون" msgid "Profile:" msgstr "پروفایل:" msgid "Host / Database information" msgstr "اطلاعات میزبان / پایگاه داده" msgid "User name:" msgstr "نام کاربر:" #, fuzzy msgid "Unable to complete email entry" msgstr "قادر به حذف دستیار \"%s\" نیست" #, python-format msgid "E-mail %s" msgstr "ایمیل %s" msgid "To:" msgstr "به:" #, fuzzy msgid "Cc:" msgstr "CC:" msgid "Bcc:" msgstr "" msgid "Subject:" msgstr "موضوع :" #, fuzzy msgid "Body" msgstr "بدنه:" #, fuzzy msgid "Reports" msgstr "گزارش" #, fuzzy msgid "Attachments" msgstr "پیوست:" msgid "Files" msgstr "" msgid "Send" msgstr "" #, fuzzy msgid "Select File" msgstr "انتخاب همه" #, fuzzy msgid "Remove File" msgstr "حذف " msgid "Select" msgstr "انتخاب" msgid "Add..." msgstr "افزودن..." #, fuzzy msgid "Preview" msgstr "قبلی" msgid "Previous" msgstr "قبلی" msgid "Next" msgstr "بعدی" #, fuzzy, python-format msgid "Attachment (%s)" msgstr "پیوستها (%s)" #, python-format msgid "Note (%d/%d)" msgstr "" msgid "You have to select one record." msgstr "شما باید یک پرونده را انتخاب کنید." msgid "Are you sure to remove this record?" msgstr "برای حذف این پرونده اطمینان کامل دارید؟" msgid "Are you sure to remove those records?" msgstr "برای حذف این پرونده ها اطمینان کامل دارید؟" msgid "Records not removed." msgstr "پرونده ها حذف نشدند." msgid "Records removed." msgstr "پرونده ها حذف شدند." msgid "Working now on the duplicated record(s)." msgstr "هم اکنون کار بر روی پرونده(ها) تکثیر شده انجام می شود." msgid "Record saved." msgstr "پرونده ذخیره شد." msgid "" "This record has been modified\n" "do you want to save it?" msgstr "" "این پرونده هنوز اصلاح نشده است.\n" "می‌خواهید آن را ذخیره کنید ؟" msgid "Launch action" msgstr "انجام اقدام" msgid "Relate" msgstr "مرتبط" msgid "Open related records" msgstr "بازکردن پرونده های مرتبط" msgid "Report" msgstr "گزارش" msgid "Open report" msgstr "باز کردن گزارش" msgid "Print" msgstr "چاپ" msgid "Print report" msgstr "چاپ گزارش" msgid "_Copy URL" msgstr "_رونوشت URL" msgid "Copy URL into clipboard" msgstr "کپی URL درون کلیپبورد" msgid "Unknown" msgstr "نامعلوم" msgid "Limit" msgstr "محدود" msgid "Search Limit Settings" msgstr "تنظیمات جستجو محدود" msgid "Limit:" msgstr "محدودیت:" #, python-format msgid "Logs (%s)" msgstr "سیاهههای مربوط (%s)" msgid "Model:" msgstr "مدل:" msgid "ID:" msgstr "شناسه:" #, fuzzy msgid "Created by:" msgstr "تاریخ ایجاد" #, fuzzy msgid "Created at:" msgstr "تاریخ ایجاد" #, fuzzy msgid "Last Modified by:" msgstr "آخرین تغییرات بوسیله:" #, fuzzy msgid "Last Modified at:" msgstr "تاریخ آخرین تغییرات:" #, python-format msgid "Notes (%s)" msgstr "یادداشت ها(%s)" msgid "Edit User Preferences" msgstr "ویرایش ظاهر برنامه کاربر" msgid "Preference" msgstr "ظاهر" msgid "Revision" msgstr "تجدید نظر" msgid "Select a revision" msgstr "انتخاب یک تجدید نظر" msgid "Revision:" msgstr "تجدید نظر:" msgid "_Switch View" msgstr "_تعویض نمایه" msgid "Switch View" msgstr "تعویض نمایه" msgid "_Previous" msgstr "_قبلی" msgid "Previous Record" msgstr "پرونده قبلی" msgid "_Next" msgstr "_بعدی" msgid "Next Record" msgstr "پرونده بعدی" msgid "_Search" msgstr "_جستجو" msgid "_New" msgstr "_جدید" msgid "Create a new record" msgstr "ایجاد پرونده جدید" msgid "_Save" msgstr "_ذخیره" msgid "Save this record" msgstr "ذخیره این پرونده" msgid "_Reload/Undo" msgstr "_بارگیری مجدد / واگرد" msgid "Reload/Undo" msgstr "بارگیری مجدد / واگرد" msgid "_Duplicate" msgstr "_تکثیر" msgid "_Delete..." msgstr "_حذف..." msgid "View _Logs..." msgstr "_مشاهده گزارشات..." msgid "Show revisions..." msgstr "مشاهده تجدیدنظرها..." msgid "A_ttachments..." msgstr "_پیوست..." msgid "Add an attachment to the record" msgstr "پیوست را به پرونده اضافه کنید" msgid "_Notes..." msgstr "_یادداشت ها..." msgid "Add a note to the record" msgstr "یک یادداشت به پرونده اضافه کنید" msgid "_Actions..." msgstr "_اقدامات..." msgid "_Relate..." msgstr "_مرتبط..." msgid "_Report..." msgstr "_گزارش..." msgid "_Print..." msgstr "_چاپ..." msgid "_E-Mail..." msgstr "_ایمیل..." #, fuzzy msgid "Send an e-mail using the record" msgstr "یک یادداشت به پرونده اضافه کنید" msgid "_Export Data..." msgstr "_صادرات داده ها..." msgid "_Import Data..." msgstr "_واردات داده ها..." msgid "Copy _URL..." msgstr "_رونوشت URL..." msgid "_Close Tab" msgstr "_بستن برگه" msgid "All fields" msgstr "همه فیلد ها" msgid "_Add" msgstr "_افزودن" msgid "_Remove" msgstr "_حذف" msgid "_Clear" msgstr "_پاکسازی" msgid "Fields selected" msgstr "فیلد های انتخابی" msgid "CSV Parameters" msgstr "مولفه های CSV" msgid "Delimiter:" msgstr "جدا کننده:" msgid "Quote char:" msgstr "کاراکترنقل قول:" msgid "Encoding:" msgstr "رمزگذاری:" msgid "Use locale format" msgstr "" msgid "Field name" msgstr "نام فیلد" #, python-format msgid "CSV Export: %s" msgstr "صادرکردن(CSV) : %s" msgid "_Save Export" msgstr "_ذخیره استخراج" #, fuzzy msgid "_URL Export" msgstr "_ذخیره استخراج" msgid "_Delete Export" msgstr "_حذف استخراج" msgid "Predefined exports" msgstr "استخراج از پیش تعریف شده" msgid "Name" msgstr "نام" msgid "Open" msgstr "باز کردن" msgid "Save" msgstr "ذخیره" #, fuzzy msgid "Listed Records" msgstr "ویرایش پرونده انتخابی " #, fuzzy msgid "Selected Records" msgstr "ویرایش پرونده انتخابی " msgid "Ignore search limit" msgstr "" #, fuzzy msgid "Add field names" msgstr "_اضافه کردن نام فیلدها" #, python-format msgid "%s (string)" msgstr "متنی %s" #, python-format msgid "%s (model name)" msgstr "نام مدل %s" #, fuzzy, python-format msgid "%s/Record Name" msgstr "نام پرونده %s" msgid "What is the name of this export?" msgstr "نام این فایل استخراجی چیست؟" #, python-format msgid "Override '%s' definition?" msgstr "بازنویسی %s تعاریف؟" #, python-format msgid "%d record saved." msgstr "%d پرونده ذخیره شد." #, python-format msgid "%d records saved." msgstr "%d پرونده های ذخیره شده." msgid "Export failed" msgstr "" msgid "Link" msgstr "پیوند" msgid "Delete" msgstr "حذف" msgid "Discard changes" msgstr "" msgid "Save and New" msgstr "" #, fuzzy msgid "Add and New" msgstr "افزودن مقدار" msgid "Add" msgstr "افزودن" msgid "Apply changes" msgstr "" msgid "Switch" msgstr "تعویض" msgid "Remove " msgstr "حذف " msgid "Create a new record " msgstr "ایجاد یک پرونده جدید " msgid "Delete selected record " msgstr "حذف پرونده انتخابی " msgid "Undelete selected record " msgstr "بازگردانی فایل انتخابی " #, python-format msgid "CSV Import: %s" msgstr "وارد کردن (CSV) : %s" msgid "_Auto-Detect" msgstr "_شناسایی - خودکار" msgid "File to Import:" msgstr "فایل برای واردکردن:" msgid "Open..." msgstr "باز کردن..." msgid "Lines to Skip:" msgstr "چشم پوشی از سطرهای:" msgid "You must select an import file first." msgstr "شما باید ابتدا فایل ورودی را انتخاب کنید." msgid "Detection failed" msgstr "" #, python-format msgid "Unknown column header \"%s\"" msgstr "" msgid "Error" msgstr "خطا" msgid "Import failed" msgstr "" #, python-format msgid "%d record imported." msgstr "%d پرونده وارد شد." #, python-format msgid "%d records imported." msgstr "%d پرونده ها وارد شدند." msgid "Search" msgstr "جستجو" msgid "New" msgstr "جدید" #, python-format msgid "Search %s" msgstr "جستجو %s" msgid "Wizard" msgstr "دستیار" msgid "ID" msgstr "شناسه" #, fuzzy msgid "Created by" msgstr "تاریخ ایجاد" #, fuzzy msgid "Created at" msgstr "تاریخ ایجاد" #, fuzzy msgid "Edited by" msgstr "ویرایش" #, fuzzy msgid "Edited at" msgstr "ویرایش" msgid "Unable to set view tree state" msgstr "قادر به تنظیم حالت نمایش درختی نیست" #, fuzzy, python-format msgid "\"%s\" is not valid according to its domain." msgstr "با توجه به دامنه : \"%s\" معتبر نیست" #, fuzzy, python-format msgid "\"%s\" is required." msgstr "\"%s\" مورد نیاز است" #, fuzzy, python-format msgid "The values of \"%s\" are not valid." msgstr "مقادیر \"%s\" معتبر نیستند" msgid "Pre-validation" msgstr "پیش-اعتبارسنجی" msgid ":" msgstr ":" msgid "Scan" msgstr "" msgid "Image Size" msgstr "اندازه تصویر" msgid "Width:" msgstr "عرض:" msgid "Height:" msgstr "ارتفاع:" msgid "PNG image (*.png)" msgstr "PNG image (*.png)" msgid "Save As" msgstr "ذخیره با نام" msgid "Image size too large." msgstr "اندازه تصویر بیش از حد بزرگ است." msgid "Copy" msgstr "" msgid "Paste" msgstr "" msgid ".." msgstr ".." msgid "Open filters" msgstr "بازکردن فیلترها" msgid "Show bookmarks of filters" msgstr "نمایش بوک مارک های فیلترها" msgid "Remove this bookmark" msgstr "حذف این بوک مارک" msgid "Bookmark this filter" msgstr "بوک مارک این فیلتر" msgid "Show active records" msgstr "نمایش پرونده های فعال" msgid "Show inactive records" msgstr "نمایش پرونده های غیرفعال" msgid "Bookmark Name:" msgstr "نام بوک مارک :" msgid "Find" msgstr "یافتن" msgid "Today" msgstr "امروز" msgid "go back" msgstr "بازگشت" msgid "go forward" msgstr "برو جلو" msgid "previous year" msgstr "سال قبل" msgid "next year" msgstr "سال بعد" msgid "Day" msgstr "" msgid "Week" msgstr "هفته" #, fuzzy msgid "Month" msgstr "نمایش ماه" msgid "Select..." msgstr "انتخاب..." msgid "Clear" msgstr "پاک کردن" msgid "All files" msgstr "همه فایل ها" msgid "Show plain text" msgstr "نمایش متن ساده" msgid "Add value" msgstr "افزودن مقدار" #, python-format msgid "Remove \"%s\"" msgstr "حذف \"%s\"" msgid "Images" msgstr "تصاویر" msgid "Add existing record" msgstr "افزودن پرونده موجود" msgid "Remove selected record" msgstr "حذف پرونده انتخابی" msgid "Open the record " msgstr "بازکردن پرونده " msgid "Clear the field " msgstr "پاکسازی فیلد " msgid "Search a record " msgstr "جستجوی یک پرونده " #, fuzzy msgid "Edit selected record" msgstr "ویرایش پرونده انتخابی " #, fuzzy msgid "Delete selected record" msgstr "حذف پرونده انتخابی" #, python-format msgid "%s%%" msgstr "%s%%" msgid "Choose a language" msgstr "" msgid "Translation" msgstr "ترجمه" msgid "Edit" msgstr "ویرایش" msgid "Fuzzy" msgstr "نامانوس" msgid "You need to save the record before adding translations." msgstr "قبل از اضافه کردن ترجمه ها شما باید پرونده را ذخیره کنید." msgid "No other language available." msgstr "هیچ زبان دیگری در دسترس نیست." msgid "#ERROR" msgstr "" msgid "Translate view" msgstr "نمایش ترجمه" ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1745680285.1241102 tryton-7.0.24/tryton/data/locale/fi/0000755000175000017500000000000015003173635015363 5ustar00cedced././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1745680285.1641107 tryton-7.0.24/tryton/data/locale/fi/LC_MESSAGES/0000755000175000017500000000000015003173635017150 5ustar00cedced././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1745680279.0 tryton-7.0.24/tryton/data/locale/fi/LC_MESSAGES/tryton.mo0000644000175000017500000000067515003173627021055 0ustar00cedced$,-Project-Id-Version: PROJECT VERSION Report-Msgid-Bugs-To: EMAIL@ADDRESS POT-Creation-Date: 2025-04-26 17:11+0200 PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE Last-Translator: FULL NAME Language: fi Language-Team: fi Plural-Forms: nplurals=2; plural=(n != 1); MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: 8bit Generated-By: Babel 2.17.0 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685599.0 tryton-7.0.24/tryton/data/locale/fi/LC_MESSAGES/tryton.po0000644000175000017500000003310614517761237021064 0ustar00cedced# Translations template for tryton. # Copyright (C) 2018 Tryton # This file is distributed under the same license as the tryton project. # FIRST AUTHOR , 2018. msgid "" msgstr "Content-Type: text/plain; charset=utf-8\n" msgid "specify alternate config file" msgstr "" msgid "development mode" msgstr "" msgid "logging everything at INFO level" msgstr "" msgid "specify the log level: DEBUG, INFO, WARNING, ERROR, CRITICAL" msgstr "" msgid "specify the file used to output logging information" msgstr "" msgid "specify the login user" msgstr "" msgid "specify the server hostname:port" msgstr "" msgid "disable thread usage" msgstr "" #, python-format msgid "Unable to set locale %s" msgstr "" #, fuzzy msgid ", " msgstr ", " msgid ",..." msgstr "" #, python-format msgid "%s (%s)" msgstr "" msgid "Select your action" msgstr "" msgid "No action defined." msgstr "" msgid "By: " msgstr "" msgid "Selection" msgstr "" msgid "Cancel" msgstr "" msgid "OK" msgstr "" msgid "Your selection:" msgstr "" msgid "Save As..." msgstr "" msgid "Do you want to proceed?" msgstr "" msgid "Always ignore this warning." msgstr "" msgid "No" msgstr "" msgid "Yes" msgstr "" msgid "Concurrency Exception" msgstr "" msgid "This record has been modified while you were editing it." msgstr "" msgid "Cancel saving" msgstr "" msgid "Compare" msgstr "" msgid "See the modified version" msgstr "" msgid "Write Anyway" msgstr "" msgid "Save your current version" msgstr "" #, python-format msgid "Compare: %s" msgstr "" msgid "Close" msgstr "" msgid "Application Error" msgstr "" msgid "Report Bug" msgstr "" #, python-format msgid "Check URL: %s" msgstr "" msgid "Unable to check for new version." msgstr "" msgid "A new version is available!" msgstr "" msgid "Download" msgstr "" msgid "Could not get a session." msgstr "" msgid "Too many requests. Try again later." msgstr "" msgid "Not found." msgstr "" msgid "Not Found." msgstr "" #, fuzzy msgid "..." msgstr "..." msgid "Search..." msgstr "" msgid "Create..." msgstr "" #, python-format msgid "Create \"%s\"..." msgstr "" msgid "Value" msgstr "" msgid "Displayed value" msgstr "" msgid "Format" msgstr "" msgid "Display format" msgstr "" msgid "Open the calendar" msgstr "" msgid "Date Format" msgstr "" msgid "Displayed date format" msgstr "" msgid "y" msgstr "" msgid "True" msgstr "" msgid "t" msgstr "" msgid "False" msgstr "" msgid "Digits" msgstr "" msgid "The number of decimal" msgstr "" msgid "Template" msgstr "" msgid "Edit..." msgstr "" msgid "View Logs..." msgstr "" msgid "Attachments..." msgstr "" msgid "Notes..." msgstr "" msgid "Actions..." msgstr "" msgid "Relate..." msgstr "" msgid "Report..." msgstr "" msgid "Print..." msgstr "" msgid "E-Mail..." msgstr "" msgid "Bold" msgstr "" msgid "Italic" msgstr "" msgid "Underline" msgstr "" msgid "Align Left" msgstr "" msgid "Align Center" msgstr "" msgid "Align Right" msgstr "" msgid "Justify" msgstr "" msgid "Foreground Color" msgstr "" msgid "Select a color" msgstr "" msgid "Y" msgstr "" msgid "M" msgstr "" msgid "w" msgstr "" msgid "d" msgstr "" msgid "h" msgstr "" msgid "m" msgstr "" msgid "s" msgstr "" msgid "Preferences..." msgstr "" msgid "Toolbar" msgstr "" msgid "Default" msgstr "" msgid "Text and Icons" msgstr "" msgid "Text" msgstr "" msgid "Icons" msgstr "" msgid "Form" msgstr "" msgid "Save Column Width" msgstr "" msgid "Save Tree State" msgstr "" msgid "Spell Checking" msgstr "" msgid "Play Sound for Code Scanner" msgstr "" msgid "PDA Mode" msgstr "" msgid "Search Limit..." msgstr "" msgid "Check Version" msgstr "" msgid "Options" msgstr "" msgid "Documentation..." msgstr "" msgid "Keyboard Shortcuts..." msgstr "" msgid "About..." msgstr "" msgid "Help" msgstr "" msgid "No result found." msgstr "" msgid "Favorites" msgstr "" msgid "Manage..." msgstr "" msgid "Action" msgstr "" msgid "" "The following action requires to close all tabs.\n" "Do you want to continue?" msgstr "" msgid "Application Shortcuts" msgstr "" msgid "Global" msgstr "" msgid "Preferences" msgstr "" msgid "Search menu" msgstr "" msgid "Toggle menu" msgstr "" msgid "Previous tab" msgstr "" msgid "Next tab" msgstr "" msgid "Shortcuts" msgstr "" msgid "Quit" msgstr "" msgid "Edition Shortcuts" msgstr "" msgid "Text Entries" msgstr "" msgid "Cut selected text" msgstr "" msgid "Copy selected text" msgstr "" msgid "Paste copied text" msgstr "" msgid "Next entry" msgstr "" msgid "Previous entry" msgstr "" msgid "Relation Entries" msgstr "" msgid "Create new relation" msgstr "" msgid "Open/Search relation" msgstr "" msgid "List Entries" msgstr "" msgid "Switch view" msgstr "" msgid "Create/Select new line" msgstr "" msgid "Open relation" msgstr "" msgid "Mark line for deletion/removal" msgstr "" msgid "Mark line for removal" msgstr "" msgid "Unmark line for deletion" msgstr "" msgid "List/Tree Shortcuts" msgstr "" msgid "Move Cursor" msgstr "" msgid "Move right" msgstr "" msgid "Move left" msgstr "" msgid "Move up" msgstr "" msgid "Move down" msgstr "" msgid "Move up of one page" msgstr "" msgid "Move down of one page" msgstr "" msgid "Move to top" msgstr "" msgid "Move to bottom" msgstr "" msgid "Move to parent" msgstr "" msgid "Edition" msgstr "" msgid "Copy selected rows" msgstr "" msgid "Insert copied rows" msgstr "" msgid "Select all" msgstr "" msgid "Unselect all" msgstr "" msgid "Select parent" msgstr "" msgid "Select/Activate current row" msgstr "" msgid "Toggle selection" msgstr "" msgid "Expand/Collapse" msgstr "" msgid "Expand row" msgstr "" msgid "Collapse row" msgstr "" msgid "Toggle row" msgstr "" msgid "Collapse all rows" msgstr "" msgid "Expand all rows" msgstr "" msgid "Close Tab" msgstr "" msgid "modularity, scalability and security" msgstr "" msgid "translator-credits" msgstr "" #, python-format msgid "Attachments (%s)" msgstr "" msgid "Code Scanner" msgstr "" msgid "Code" msgstr "" msgid "Profile Editor" msgstr "" msgid "Profile" msgstr "" msgid "Add new profile" msgstr "" msgid "Remove selected profile" msgstr "" msgid "Host:" msgstr "" msgid "Database:" msgstr "" msgid "Fetching databases list" msgstr "" msgid "Username:" msgstr "" msgid "Incompatible version of the server." msgstr "" msgid "Could not connect to the server." msgstr "" msgid "Login" msgstr "" msgid "_Cancel" msgstr "" msgid "Cancel connection to the Tryton server" msgstr "" msgid "C_onnect" msgstr "" msgid "Connect the Tryton server" msgstr "" msgid "Profile:" msgstr "" msgid "Host / Database information" msgstr "" msgid "User name:" msgstr "" msgid "Unable to complete email entry" msgstr "" #, python-format msgid "E-mail %s" msgstr "" msgid "To:" msgstr "" msgid "Cc:" msgstr "" msgid "Bcc:" msgstr "" msgid "Subject:" msgstr "" msgid "Body" msgstr "" msgid "Reports" msgstr "" msgid "Attachments" msgstr "" msgid "Files" msgstr "" msgid "Send" msgstr "" msgid "Select File" msgstr "" msgid "Remove File" msgstr "" msgid "Select" msgstr "" msgid "Add..." msgstr "" msgid "Preview" msgstr "" msgid "Previous" msgstr "" msgid "Next" msgstr "" #, python-format msgid "Attachment (%s)" msgstr "" #, python-format msgid "Note (%d/%d)" msgstr "" msgid "You have to select one record." msgstr "" msgid "Are you sure to remove this record?" msgstr "" msgid "Are you sure to remove those records?" msgstr "" msgid "Records not removed." msgstr "" msgid "Records removed." msgstr "" msgid "Working now on the duplicated record(s)." msgstr "" msgid "Record saved." msgstr "" msgid "" "This record has been modified\n" "do you want to save it?" msgstr "" msgid "Launch action" msgstr "" msgid "Relate" msgstr "" msgid "Open related records" msgstr "" msgid "Report" msgstr "" msgid "Open report" msgstr "" msgid "Print" msgstr "" msgid "Print report" msgstr "" msgid "_Copy URL" msgstr "" msgid "Copy URL into clipboard" msgstr "" msgid "Unknown" msgstr "" msgid "Limit" msgstr "" msgid "Search Limit Settings" msgstr "" msgid "Limit:" msgstr "" #, python-format msgid "Logs (%s)" msgstr "" msgid "Model:" msgstr "" msgid "ID:" msgstr "" msgid "Created by:" msgstr "" msgid "Created at:" msgstr "" msgid "Last Modified by:" msgstr "" msgid "Last Modified at:" msgstr "" #, python-format msgid "Notes (%s)" msgstr "" msgid "Edit User Preferences" msgstr "" msgid "Preference" msgstr "" msgid "Revision" msgstr "" msgid "Select a revision" msgstr "" msgid "Revision:" msgstr "" msgid "_Switch View" msgstr "" msgid "Switch View" msgstr "" msgid "_Previous" msgstr "" msgid "Previous Record" msgstr "" msgid "_Next" msgstr "" msgid "Next Record" msgstr "" msgid "_Search" msgstr "" msgid "_New" msgstr "" msgid "Create a new record" msgstr "" msgid "_Save" msgstr "" msgid "Save this record" msgstr "" msgid "_Reload/Undo" msgstr "" msgid "Reload/Undo" msgstr "" msgid "_Duplicate" msgstr "" msgid "_Delete..." msgstr "" msgid "View _Logs..." msgstr "" msgid "Show revisions..." msgstr "" msgid "A_ttachments..." msgstr "" msgid "Add an attachment to the record" msgstr "" msgid "_Notes..." msgstr "" msgid "Add a note to the record" msgstr "" msgid "_Actions..." msgstr "" msgid "_Relate..." msgstr "" msgid "_Report..." msgstr "" msgid "_Print..." msgstr "" msgid "_E-Mail..." msgstr "" msgid "Send an e-mail using the record" msgstr "" msgid "_Export Data..." msgstr "" msgid "_Import Data..." msgstr "" msgid "Copy _URL..." msgstr "" msgid "_Close Tab" msgstr "" msgid "All fields" msgstr "" msgid "_Add" msgstr "" msgid "_Remove" msgstr "" msgid "_Clear" msgstr "" msgid "Fields selected" msgstr "" msgid "CSV Parameters" msgstr "" msgid "Delimiter:" msgstr "" msgid "Quote char:" msgstr "" msgid "Encoding:" msgstr "" msgid "Use locale format" msgstr "" msgid "Field name" msgstr "" #, python-format msgid "CSV Export: %s" msgstr "" msgid "_Save Export" msgstr "" msgid "_URL Export" msgstr "" msgid "_Delete Export" msgstr "" msgid "Predefined exports" msgstr "" msgid "Name" msgstr "" msgid "Open" msgstr "" msgid "Save" msgstr "" msgid "Listed Records" msgstr "" msgid "Selected Records" msgstr "" msgid "Ignore search limit" msgstr "" msgid "Add field names" msgstr "" #, python-format msgid "%s (string)" msgstr "" #, python-format msgid "%s (model name)" msgstr "" #, python-format msgid "%s/Record Name" msgstr "" msgid "What is the name of this export?" msgstr "" #, python-format msgid "Override '%s' definition?" msgstr "" #, python-format msgid "%d record saved." msgstr "" #, python-format msgid "%d records saved." msgstr "" msgid "Export failed" msgstr "" msgid "Link" msgstr "" msgid "Delete" msgstr "" msgid "Discard changes" msgstr "" msgid "Save and New" msgstr "" msgid "Add and New" msgstr "" msgid "Add" msgstr "" msgid "Apply changes" msgstr "" msgid "Switch" msgstr "" msgid "Remove " msgstr "" msgid "Create a new record " msgstr "" msgid "Delete selected record " msgstr "" msgid "Undelete selected record " msgstr "" #, python-format msgid "CSV Import: %s" msgstr "" msgid "_Auto-Detect" msgstr "" msgid "File to Import:" msgstr "" msgid "Open..." msgstr "" msgid "Lines to Skip:" msgstr "" msgid "You must select an import file first." msgstr "" msgid "Detection failed" msgstr "" #, python-format msgid "Unknown column header \"%s\"" msgstr "" msgid "Error" msgstr "" msgid "Import failed" msgstr "" #, python-format msgid "%d record imported." msgstr "" #, python-format msgid "%d records imported." msgstr "" msgid "Search" msgstr "" msgid "New" msgstr "" #, python-format msgid "Search %s" msgstr "" msgid "Wizard" msgstr "" msgid "ID" msgstr "" msgid "Created by" msgstr "" msgid "Created at" msgstr "" msgid "Edited by" msgstr "" msgid "Edited at" msgstr "" msgid "Unable to set view tree state" msgstr "" #, python-format msgid "\"%s\" is not valid according to its domain." msgstr "" #, python-format msgid "\"%s\" is required." msgstr "" #, python-format msgid "The values of \"%s\" are not valid." msgstr "" msgid "Pre-validation" msgstr "" #, fuzzy msgid ":" msgstr ":" msgid "Scan" msgstr "" msgid "Image Size" msgstr "" msgid "Width:" msgstr "" msgid "Height:" msgstr "" msgid "PNG image (*.png)" msgstr "" msgid "Save As" msgstr "" msgid "Image size too large." msgstr "" msgid "Copy" msgstr "" msgid "Paste" msgstr "" #, fuzzy msgid ".." msgstr ".." msgid "Open filters" msgstr "" msgid "Show bookmarks of filters" msgstr "" msgid "Remove this bookmark" msgstr "" msgid "Bookmark this filter" msgstr "" msgid "Show active records" msgstr "" msgid "Show inactive records" msgstr "" msgid "Bookmark Name:" msgstr "" msgid "Find" msgstr "" msgid "Today" msgstr "" msgid "go back" msgstr "" msgid "go forward" msgstr "" msgid "previous year" msgstr "" msgid "next year" msgstr "" msgid "Day" msgstr "" msgid "Week" msgstr "" msgid "Month" msgstr "" msgid "Select..." msgstr "" msgid "Clear" msgstr "" msgid "All files" msgstr "" msgid "Show plain text" msgstr "" msgid "Add value" msgstr "" #, python-format msgid "Remove \"%s\"" msgstr "" msgid "Images" msgstr "" msgid "Add existing record" msgstr "" msgid "Remove selected record" msgstr "" msgid "Open the record " msgstr "" msgid "Clear the field " msgstr "" msgid "Search a record " msgstr "" msgid "Edit selected record" msgstr "" msgid "Delete selected record" msgstr "" #, python-format msgid "%s%%" msgstr "" msgid "Choose a language" msgstr "" msgid "Translation" msgstr "" msgid "Edit" msgstr "" msgid "Fuzzy" msgstr "" msgid "You need to save the record before adding translations." msgstr "" msgid "No other language available." msgstr "" msgid "#ERROR" msgstr "" msgid "Translate view" msgstr "" ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1745680285.1241102 tryton-7.0.24/tryton/data/locale/fr/0000755000175000017500000000000015003173635015374 5ustar00cedced././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1745680285.1641107 tryton-7.0.24/tryton/data/locale/fr/LC_MESSAGES/0000755000175000017500000000000015003173635017161 5ustar00cedced././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1745680279.0 tryton-7.0.24/tryton/data/locale/fr/LC_MESSAGES/tryton.mo0000644000175000017500000004743315003173627021071 0ustar00cedcedL |*}  %16EHMPTVh  ?K_o      #%4Z jv&  2@ D R`rx     0=P c    &2 D NZ^fm  !2 ; EOTj        & 1 A G L ] b i o v ~         #  !3!:!B!X!j! |!!!!! !!!! !! !!","3" 9" E"O" e" o"z"" """""" " """"#!# 2# =# H# U#`#i#l# q#~# # #######$$.$4$F$b$ q$ |$$$$$$ $$ $$$$%% % !%/%D%U% \%f% w% % % %%%%% % %&& &#&(& 0&;&M& ]&j&{&&& &&&& && ' ''-' ?' J'X' k'u'' ''' ''' ((/(A(P(Y( `( l(x(( ((I((!)5$)8Z))) ) ))#))) * * *2*Q*i** **** ** + + + &+ 3+A+ F+g+n+(u+ ++++%+7+., >,J, O,\,d, k, v,, , , ,,,,, , , , , -- -$- *-7- ?- L-X-Z-k-- -- --$- - ---3.<I.. .......f0000001"1*1 >1K1P1k1n1s1v1z1~11!111%12 22?2 F2Q2$Y2*~22"2223 )343<3N3`3#r3333:3;4X4i4y44444444445 55&$5K5a5h55555555556%6.6=6X6w6#~66!66&6!7<7Q7#q77'77 7 7 78%898H8M8 U8(_8-8888899-9>9Z9 k9 y9 99%9&9 9 9 9: : $:.:5:P:b:s:::0: ::::: ;;%;+; 2;=; B;O;X;[;a;i;;;;; ;;< <%<;<X<v<< <<<<<< < == =-=!I= k=v={== ==== >>2>!I>k>o>w>>>>>>>> ? ? #? 0?;?D?L?S? f????? ??@'@6@?@Q@X@$o@@ @ @@@ @@@A'A0A DAPAWA jAtA|AAAAA AAB%B:BJB"_B(BBBB BB B B CC'C!;C]C|CC"CC C CCDD>DVD pD~DDDDDD E'EFE eEpE/xE E"E%E$E!F7FRFjFsF|FFFFFFQF G.6G:eGRGG GHH(H'?HgHvH HH/H+H(H.I-JI xII&I0IIIJ&J;JBJWJmJ uJ J J2JJJJ+J5KAOKK KKKKKK KL L *L 5L@LWLnLwL L L L LLL L LL LMM*M,M%@MfMnMvMxMM(MMMM1MPNCgN)N!NNNOO"%s" is not valid according to its domain."%s" is required.#ERROR%d record imported.%d record saved.%d records imported.%d records saved.%s (%s)%s (model name)%s (string)%s%%%s/Record Name, ,........:All fieldsFields selectedPredefined exportsCreate...Search...A new version is available!A_ttachments...About...ActionActions...AddAdd a note to the recordAdd an attachment to the recordAdd and NewAdd existing recordAdd field namesAdd new profileAdd valueAdd...Align CenterAlign LeftAlign RightAll filesAlways ignore this warning.Application ErrorApplication ShortcutsApply changesAre you sure to remove this record?Are you sure to remove those records?Attachment (%s)AttachmentsAttachments (%s)Attachments...Bcc:BodyBoldBookmark Name:Bookmark this filterBy: CSV Export: %sCSV Import: %sCSV ParametersC_onnectCancelCancel connection to the Tryton serverCancel savingCc:Check URL: %sCheck VersionChoose a languageClearClear the field CloseClose TabCodeCode ScannerCollapse all rowsCollapse rowCompareCompare: %sConcurrency ExceptionConnect the Tryton serverCopyCopy URL into clipboardCopy _URL...Copy selected rowsCopy selected textCould not connect to the server.Could not get a session.Create "%s"...Create a new recordCreate a new record Create new relationCreate/Select new lineCreated atCreated at:Created byCreated by:Cut selected textDatabase:Date FormatDayDefaultDeleteDelete selected recordDelete selected record Delimiter:Detection failedDigitsDiscard changesDisplay formatDisplayed date formatDisplayed valueDo you want to proceed?Documentation...DownloadE-Mail...E-mail %sEditEdit User PreferencesEdit selected recordEdit...Edited atEdited byEditionEdition ShortcutsEncoding:ErrorExpand all rowsExpand rowExpand/CollapseExport failedFalseFavoritesFetching databases listField nameFile to Import:FilesFindForeground ColorFormFormatFuzzyGlobalHeight:HelpHost / Database informationHost:IDID:IconsIgnore search limitImage SizeImage size too large.ImagesImport failedIncompatible version of the server.Insert copied rowsItalicJustifyKeyboard Shortcuts...Last Modified at:Last Modified by:Launch actionLimitLimit:Lines to Skip:LinkList EntriesList/Tree ShortcutsListed RecordsLoginLogs (%s)MManage...Mark line for deletion/removalMark line for removalModel:MonthMove CursorMove downMove down of one pageMove leftMove rightMove to bottomMove to parentMove to topMove upMove up of one pageNameNewNextNext RecordNext entryNext tabNoNo action defined.No other language available.No result found.Not Found.Not found.Note (%d/%d)Notes (%s)Notes...OKOpenOpen filtersOpen related recordsOpen relationOpen reportOpen the calendarOpen the record Open...Open/Search relationOptionsOverride '%s' definition?PDA ModePNG image (*.png)PastePaste copied textPlay Sound for Code ScannerPre-validationPreferencePreferencesPreferences...PreviewPreviousPrevious RecordPrevious entryPrevious tabPrintPrint reportPrint...ProfileProfile EditorProfile:QuitQuote char:Record saved.Records not removed.Records removed.RelateRelate...Relation EntriesReload/UndoRemove "%s"Remove Remove FileRemove selected profileRemove selected recordRemove this bookmarkReportReport BugReport...ReportsRevisionRevision:SaveSave AsSave As...Save Column WidthSave Tree StateSave and NewSave this recordSave your current versionScanSearchSearch %sSearch Limit SettingsSearch Limit...Search a record Search menuSee the modified versionSelectSelect FileSelect a colorSelect a revisionSelect allSelect parentSelect your actionSelect...Select/Activate current rowSelected RecordsSelectionSendSend an e-mail using the recordShortcutsShow active recordsShow bookmarks of filtersShow inactive recordsShow plain textShow revisions...Spell CheckingSubject:SwitchSwitch ViewSwitch viewTemplateTextText EntriesText and IconsThe following action requires to close all tabs. Do you want to continue?The number of decimalThe values of "%s" are not valid.This record has been modified do you want to save it?This record has been modified while you were editing it.To:TodayToggle menuToggle rowToggle selectionToo many requests. Try again later.ToolbarTranslate viewTranslationTrueUnable to check for new version.Unable to complete email entryUnable to set locale %sUnable to set view tree stateUndelete selected record UnderlineUnknownUnknown column header "%s"Unmark line for deletionUnselect allUse locale formatUser name:Username:ValueView Logs...View _Logs...WeekWhat is the name of this export?Width:WizardWorking now on the duplicated record(s).Write AnywayYYesYou have to select one record.You must select an import file first.You need to save the record before adding translations.Your selection:_Actions..._Add_Auto-Detect_Cancel_Clear_Close Tab_Copy URL_Delete Export_Delete..._Duplicate_E-Mail..._Export Data..._Import Data..._New_Next_Notes..._Previous_Print..._Relate..._Reload/Undo_Remove_Report..._Save_Save Export_Search_Switch View_URL Exportddevelopment modedisable thread usagego backgo forwardhlogging everything at INFO levelmmodularity, scalability and securitynext yearprevious yearsspecify alternate config filespecify the file used to output logging informationspecify the log level: DEBUG, INFO, WARNING, ERROR, CRITICALspecify the login userspecify the server hostname:portttranslator-creditswyProject-Id-Version: PROJECT VERSION Report-Msgid-Bugs-To: EMAIL@ADDRESS POT-Creation-Date: 2025-04-26 17:11+0200 PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE Last-Translator: FULL NAME Language: fr Language-Team: fr Plural-Forms: nplurals=2; plural=(n > 1); MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: 8bit Generated-By: Babel 2.17.0 « %s » n'est pas valide selon son domaine.« %s » est requis.#ERREUR%d enregistrement importé.%d entrée enregistrée.%d enregistrements importés.%d entrées enregistrées.%s (%s)%s (nom du modèle)%s (chaîne)%s%%%s/Nom de l'enregistrement, ,........ :Tout les champsChamps sélectionnésExportations prédéfiniesCréer...Recherche...Un nouvelle version est disponible !_Pièce jointes...À Propos...ActionActions...AjouterAjouter une note à l'enregistrementAjouter un attachement à l'enregistrementAjouter et nouveauAjouter un enregistrement existantAjouter les noms des champsAjouter un nouveau profileAjouter une valeurAjouter...CentrerAligner à gaucheAligner à droiteTous les fichiersToujours ignorer cet avertissement.Erreur applicativeRaccourcis de l'applicationAppliquer les modificationsÊtes-vous sûr de vouloir supprimer cet enregistrement ?Êtes-vous sûr de vouloir supprimer ces enregistrements ?Attachement (%s)Pièces jointesAttachements (%s)Attachements...Cci :CorpsGrasNom du marque-page :Sauver ce filtrePar : Export CSV : %sImport CSV : %sParamètres CSVC_onnecterAnnulerAnnuler la connexion au serveur TrytonAnnuler la sauvegardeCc :Vérification de l'URL : %sVérifier la versionChoisir une langueEffacerEffacer le champ FermerFermer l'ongletCodeScanner de codesReplier toutes les lignesReplier la ligneComparerComparer : %sErreur d'accès concurrentSe connecter au serveur TrytonCopierCopier l'URL dans le presse-papiersCopier l'_URL...Copier les lignes sélectionnéesCopier le texte sélectionnéImpossible de se connecter au serveur.Impossible d'obtenir une session.Créer « %s »...Créer un nouvel enregistrementCréer un nouvel enregistrementCréer une nouvelle relationCréer/Sélectionner une nouvelle ligneCrée leCrée le :Créé parCrée par :Couper le texte sélectionnéBase de données :Format de dateJourDéfautSupprimerSupprimer l'enregistrement sélectionnéSupprimer l'enregistrement sélectionnéDélimiteur :La détection a échouéChiffresAnnuler les modificationsFormat d'affichageFormat de date affichéeValeur affichéeSouhaitez-vous continuer ?Documentation...TéléchargerE-mail...E-mail %sÉditerÉditer les préférences utilisateurÉditer l'enregistrement sélectionnéÉditer...Édité leÉdité parÉditionÉditer les raccourcisCodage :ErreurDéplier toutes les lignesDéplier la ligneDéplier/ReplierL'export a échouéFauxFavorisRécupération de la liste des bases de donnéesNom du champFichier à importer :FichiersChercherCouleur de premier planFormulaireFormatFloueGlobalHauteur :AideHôte / PortHôte :IDID :IcônesIgnorer la limite de rechercheTaille de l'imageTaille de l'image trop grande.ImagesL'import a échouéVersion du serveur incompatible.Insérer des lignes copiéesItaliqueJustifierRaccourcis clavier...Dernière modification à :Dernière modification par :Lancer une actionLimiteLimite :Lignes à ignorer :LienEntrées de listeRaccourcis de liste/arbreEnregistrements listésConnexionJournaux (%s)MGérer...Marquer la ligne pour suppression/enlèvementMarquer la ligne pour enlèvementModèle :MoisDéplacer le curseurDéplacer vers le basDéplacer vers le bas d'une pageDéplacer vers la gaucheDéplacer vers la droiteDéplacer en basDéplacer au parentDéplacer au dessusDéplacer vers le hautDéplacer vers le haut d'une pageNomNouveauSuivantEnregistrement suivantEntrée suivanteOnglet suivantNonPas d'action définie.Pas d'autre langue disponible.Aucun résultat trouvé.Pas trouvé.Pas trouvé.Note (%d/%d)Notes (%s)Notes...ValiderOuvrirOuvrir les filtresOuvrir les enregistrements liésOuvrir une relationOuvrir un rapportOuvrir le calendrierOuvrir l'enregistrement Ouvrir...Ouvrir/Chercher une relationOptionsSurcharger la définition « %s » ?Mode PDAimage PNG (*.png)CollerColler le texte copiéJouer du son pour le scanner de codePré-validationPréférencePréférencesPréférences...AperçuPrécédentEnregistrement précédentEntrée précédenteOnglet précédentImprimerImprimer un rapportImprimer...ProfilÉditeur de profilProfil :QuitterCaractère de guillemet :Entrée enregistrée.Enregistrements non supprimés.Enregistrements supprimés.RelationRelation...Entrées de relationRecharger/AnnulerSupprimer « %s »Supprimer Supprimer le fichierSupprimer le profile sélectionnéSupprimer l'enregistrement sélectionnéSupprimer ce marque-pageRapportRapporter un bogueRapport...RapportsRévisionRévision :EnregistrerEnregistrer sousEnregistrer sous...Enregistrer la largeur de colonneEnregistrer l'état des arbresEnregistrer et nouveauEnregistrer cette entréeEnregistrer votre version actuelleScannerRechercherRechercher %sLimite de rechercheLimite la recherche à...Chercher un enregistrementRechercher dans le menuVoir la version modifiéeSélectionnerChoisir un fichierSélectionner une couleurSélectionner une révisionTout sélectionnerSélectionner le parentSélectionnez votre actionSélectionner...Sélectionner/Activer la ligne couranteEnregistrements sélectionnésSélectionEnvoyerEnvoyer un e-mail en utilisant l'enregistrementRaccourcisMontrer les enregistrements actifsMontrer les marques-pages des filtresMontrer les enregistrements inactifsMontrer en text clairAfficher les révisions...Vérifier l'orthographeObjet :BasculerChanger de vueBasculer la vueModèleTextesEntrées de texteTextes et IcônesL'action suivante nécessite de fermer tous les onglets. Voulez-vous continuer ?Le nombre de décimalLes valeurs de « %s » ne sont pas valides.Cet entrée a été modifiée voulez-vous l'enregistrer ?Cet enregistrement a été modifié pendant que vous étiez en train de l'éditer.À :Aujourd'huiBasculer le menuBasculer la ligneBasculer la sélectionTrop de demandes. Réessayez plus tard.Barre d'outilsTraduire la vueTraductionVraiImpossible de vérifier les nouvelles versions.Impossible de compléter l'entrée d'e-mailImpossible de sélectionner la locale %sImpossible de définir l'état de la vue arbreRestaurer l'enregistrement sélectionnéSoulignéInconnuEn-tête de colonne inconnu « %s »Annuler le marquage de la ligne pour suppressionDésélectionner toutUtiliser le format localNom d'utilisateur :Nom d'utilisateur :ValeurVoir les journaux...Voir les _journaux...SemaineQuel est le nom de cet export ?Largeur :AssistantSur les enregistrement(s) dupliqué(s) maintenant.ÉcraserAOuiVous devez sélectionner un enregistrement.Vous devez d'abord sélectionner un fichier d'import.Vous devez enregistrer l'entrée avant d'ajouter des traductions.Votre sélection :_Actions..._AjouterDétection _automatiqueA_nnuler_Effacer_Fermer l'onglet_Copier l'URL_Supprimer l'exportationSu_pprimer..._Dupliquer_E-mail..._Export de données..._Import de données..._NouveauSui_vant_Notes..._Précédent_Imprimer..._Relation..._Recharger/Annuler_Enlever_Rapport...Enregi_strerEnregi_strer l'export_Recherche_Changer de vue_URL d'exportationjmode développementdésactiver l'utilisation des threadsreveniravancerhlogger tout au niveau INFOmmodularité, évolutivité et sécuritéannée suivanteannée précédentesspécifier un fichier de configuration alternatifspécifier le fichier utilisé pour générer les informations de journalisationspécifie le niveau de log : DEBUG, INFO, WARNING, ERROR, CRITICALspécifier l'identifiant de l'utilisateurspécifier le nom du serveur:portvCédric Krier Nicolas ÉvrardSy././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685599.0 tryton-7.0.24/tryton/data/locale/fr/LC_MESSAGES/tryton.po0000644000175000017500000005157714517761237021111 0ustar00cedced# French (France) translations for tryton. # Copyright (C) 2008-2013 B2CK # This file is distributed under the same license as the tryton project. # FIRST AUTHOR , 2008-2013. msgid "" msgstr "Content-Type: text/plain; charset=utf-8\n" msgid "specify alternate config file" msgstr "spécifier un fichier de configuration alternatif" msgid "development mode" msgstr "mode développement" msgid "logging everything at INFO level" msgstr "logger tout au niveau INFO" msgid "specify the log level: DEBUG, INFO, WARNING, ERROR, CRITICAL" msgstr "spécifie le niveau de log : DEBUG, INFO, WARNING, ERROR, CRITICAL" msgid "specify the file used to output logging information" msgstr "" "spécifier le fichier utilisé pour générer les informations de journalisation" msgid "specify the login user" msgstr "spécifier l'identifiant de l'utilisateur" msgid "specify the server hostname:port" msgstr "spécifier le nom du serveur:port" msgid "disable thread usage" msgstr "désactiver l'utilisation des threads" #, python-format msgid "Unable to set locale %s" msgstr "Impossible de sélectionner la locale %s" msgid ", " msgstr ", " msgid ",..." msgstr ",..." #, python-format msgid "%s (%s)" msgstr "%s (%s)" msgid "Select your action" msgstr "Sélectionnez votre action" msgid "No action defined." msgstr "Pas d'action définie." msgid "By: " msgstr "Par : " msgid "Selection" msgstr "Sélection" msgid "Cancel" msgstr "Annuler" msgid "OK" msgstr "Valider" msgid "Your selection:" msgstr "Votre sélection :" msgid "Save As..." msgstr "Enregistrer sous..." msgid "Do you want to proceed?" msgstr "Souhaitez-vous continuer ?" msgid "Always ignore this warning." msgstr "Toujours ignorer cet avertissement." msgid "No" msgstr "Non" msgid "Yes" msgstr "Oui" msgid "Concurrency Exception" msgstr "Erreur d'accès concurrent" msgid "This record has been modified while you were editing it." msgstr "" "Cet enregistrement a été modifié pendant que vous étiez en train de " "l'éditer." msgid "Cancel saving" msgstr "Annuler la sauvegarde" msgid "Compare" msgstr "Comparer" msgid "See the modified version" msgstr "Voir la version modifiée" msgid "Write Anyway" msgstr "Écraser" msgid "Save your current version" msgstr "Enregistrer votre version actuelle" #, python-format msgid "Compare: %s" msgstr "Comparer : %s" msgid "Close" msgstr "Fermer" msgid "Application Error" msgstr "Erreur applicative" msgid "Report Bug" msgstr "Rapporter un bogue" #, python-format msgid "Check URL: %s" msgstr "Vérification de l'URL : %s" msgid "Unable to check for new version." msgstr "Impossible de vérifier les nouvelles versions." msgid "A new version is available!" msgstr "Un nouvelle version est disponible !" msgid "Download" msgstr "Télécharger" msgid "Could not get a session." msgstr "Impossible d'obtenir une session." msgid "Too many requests. Try again later." msgstr "Trop de demandes. Réessayez plus tard." msgid "Not found." msgstr "Pas trouvé." msgid "Not Found." msgstr "Pas trouvé." msgid "..." msgstr "..." msgid "Search..." msgstr "Recherche..." msgid "Create..." msgstr "Créer..." #, python-format msgid "Create \"%s\"..." msgstr "Créer « %s »..." msgid "Value" msgstr "Valeur" msgid "Displayed value" msgstr "Valeur affichée" msgid "Format" msgstr "Format" msgid "Display format" msgstr "Format d'affichage" msgid "Open the calendar" msgstr "Ouvrir le calendrier" msgid "Date Format" msgstr "Format de date" msgid "Displayed date format" msgstr "Format de date affichée" msgid "y" msgstr "y" msgid "True" msgstr "Vrai" msgid "t" msgstr "v" msgid "False" msgstr "Faux" msgid "Digits" msgstr "Chiffres" msgid "The number of decimal" msgstr "Le nombre de décimal" msgid "Template" msgstr "Modèle" msgid "Edit..." msgstr "Éditer..." msgid "View Logs..." msgstr "Voir les journaux..." msgid "Attachments..." msgstr "Attachements..." msgid "Notes..." msgstr "Notes..." msgid "Actions..." msgstr "Actions..." msgid "Relate..." msgstr "Relation..." msgid "Report..." msgstr "Rapport..." msgid "Print..." msgstr "Imprimer..." msgid "E-Mail..." msgstr "E-mail..." msgid "Bold" msgstr "Gras" msgid "Italic" msgstr "Italique" msgid "Underline" msgstr "Souligné" msgid "Align Left" msgstr "Aligner à gauche" msgid "Align Center" msgstr "Centrer" msgid "Align Right" msgstr "Aligner à droite" msgid "Justify" msgstr "Justifier" msgid "Foreground Color" msgstr "Couleur de premier plan" msgid "Select a color" msgstr "Sélectionner une couleur" msgid "Y" msgstr "A" msgid "M" msgstr "M" msgid "w" msgstr "S" msgid "d" msgstr "j" msgid "h" msgstr "h" msgid "m" msgstr "m" msgid "s" msgstr "s" msgid "Preferences..." msgstr "Préférences..." msgid "Toolbar" msgstr "Barre d'outils" msgid "Default" msgstr "Défaut" msgid "Text and Icons" msgstr "Textes et Icônes" msgid "Text" msgstr "Textes" msgid "Icons" msgstr "Icônes" msgid "Form" msgstr "Formulaire" msgid "Save Column Width" msgstr "Enregistrer la largeur de colonne" msgid "Save Tree State" msgstr "Enregistrer l'état des arbres" msgid "Spell Checking" msgstr "Vérifier l'orthographe" msgid "Play Sound for Code Scanner" msgstr "Jouer du son pour le scanner de code" msgid "PDA Mode" msgstr "Mode PDA" msgid "Search Limit..." msgstr "Limite la recherche à..." msgid "Check Version" msgstr "Vérifier la version" msgid "Options" msgstr "Options" msgid "Documentation..." msgstr "Documentation..." msgid "Keyboard Shortcuts..." msgstr "Raccourcis clavier..." msgid "About..." msgstr "À Propos..." msgid "Help" msgstr "Aide" msgid "No result found." msgstr "Aucun résultat trouvé." msgid "Favorites" msgstr "Favoris" msgid "Manage..." msgstr "Gérer..." msgid "Action" msgstr "Action" msgid "" "The following action requires to close all tabs.\n" "Do you want to continue?" msgstr "" "L'action suivante nécessite de fermer tous les onglets.\n" "Voulez-vous continuer ?" msgid "Application Shortcuts" msgstr "Raccourcis de l'application" msgid "Global" msgstr "Global" msgid "Preferences" msgstr "Préférences" msgid "Search menu" msgstr "Rechercher dans le menu" msgid "Toggle menu" msgstr "Basculer le menu" msgid "Previous tab" msgstr "Onglet précédent" msgid "Next tab" msgstr "Onglet suivant" msgid "Shortcuts" msgstr "Raccourcis" msgid "Quit" msgstr "Quitter" msgid "Edition Shortcuts" msgstr "Éditer les raccourcis" msgid "Text Entries" msgstr "Entrées de texte" msgid "Cut selected text" msgstr "Couper le texte sélectionné" msgid "Copy selected text" msgstr "Copier le texte sélectionné" msgid "Paste copied text" msgstr "Coller le texte copié" msgid "Next entry" msgstr "Entrée suivante" msgid "Previous entry" msgstr "Entrée précédente" msgid "Relation Entries" msgstr "Entrées de relation" msgid "Create new relation" msgstr "Créer une nouvelle relation" msgid "Open/Search relation" msgstr "Ouvrir/Chercher une relation" msgid "List Entries" msgstr "Entrées de liste" msgid "Switch view" msgstr "Basculer la vue" msgid "Create/Select new line" msgstr "Créer/Sélectionner une nouvelle ligne" msgid "Open relation" msgstr "Ouvrir une relation" msgid "Mark line for deletion/removal" msgstr "Marquer la ligne pour suppression/enlèvement" msgid "Mark line for removal" msgstr "Marquer la ligne pour enlèvement" msgid "Unmark line for deletion" msgstr "Annuler le marquage de la ligne pour suppression" msgid "List/Tree Shortcuts" msgstr "Raccourcis de liste/arbre" msgid "Move Cursor" msgstr "Déplacer le curseur" msgid "Move right" msgstr "Déplacer vers la droite" msgid "Move left" msgstr "Déplacer vers la gauche" msgid "Move up" msgstr "Déplacer vers le haut" msgid "Move down" msgstr "Déplacer vers le bas" msgid "Move up of one page" msgstr "Déplacer vers le haut d'une page" msgid "Move down of one page" msgstr "Déplacer vers le bas d'une page" msgid "Move to top" msgstr "Déplacer au dessus" msgid "Move to bottom" msgstr "Déplacer en bas" msgid "Move to parent" msgstr "Déplacer au parent" msgid "Edition" msgstr "Édition" msgid "Copy selected rows" msgstr "Copier les lignes sélectionnées" msgid "Insert copied rows" msgstr "Insérer des lignes copiées" msgid "Select all" msgstr "Tout sélectionner" msgid "Unselect all" msgstr "Désélectionner tout" msgid "Select parent" msgstr "Sélectionner le parent" msgid "Select/Activate current row" msgstr "Sélectionner/Activer la ligne courante" msgid "Toggle selection" msgstr "Basculer la sélection" msgid "Expand/Collapse" msgstr "Déplier/Replier" msgid "Expand row" msgstr "Déplier la ligne" msgid "Collapse row" msgstr "Replier la ligne" msgid "Toggle row" msgstr "Basculer la ligne" msgid "Collapse all rows" msgstr "Replier toutes les lignes" msgid "Expand all rows" msgstr "Déplier toutes les lignes" msgid "Close Tab" msgstr "Fermer l'onglet" msgid "modularity, scalability and security" msgstr "modularité, évolutivité et sécurité" msgid "translator-credits" msgstr "" "Cédric Krier\n" "Nicolas Évrard" #, python-format msgid "Attachments (%s)" msgstr "Attachements (%s)" msgid "Code Scanner" msgstr "Scanner de codes" msgid "Code" msgstr "Code" msgid "Profile Editor" msgstr "Éditeur de profil" msgid "Profile" msgstr "Profil" msgid "Add new profile" msgstr "Ajouter un nouveau profile" msgid "Remove selected profile" msgstr "Supprimer le profile sélectionné" msgid "Host:" msgstr "Hôte :" msgid "Database:" msgstr "Base de données :" msgid "Fetching databases list" msgstr "Récupération de la liste des bases de données" msgid "Username:" msgstr "Nom d'utilisateur :" msgid "Incompatible version of the server." msgstr "Version du serveur incompatible." msgid "Could not connect to the server." msgstr "Impossible de se connecter au serveur." msgid "Login" msgstr "Connexion" msgid "_Cancel" msgstr "A_nnuler" msgid "Cancel connection to the Tryton server" msgstr "Annuler la connexion au serveur Tryton" msgid "C_onnect" msgstr "C_onnecter" msgid "Connect the Tryton server" msgstr "Se connecter au serveur Tryton" msgid "Profile:" msgstr "Profil :" msgid "Host / Database information" msgstr "Hôte / Port" msgid "User name:" msgstr "Nom d'utilisateur :" msgid "Unable to complete email entry" msgstr "Impossible de compléter l'entrée d'e-mail" #, python-format msgid "E-mail %s" msgstr "E-mail %s" msgid "To:" msgstr "À :" msgid "Cc:" msgstr "Cc :" msgid "Bcc:" msgstr "Cci :" msgid "Subject:" msgstr "Objet :" msgid "Body" msgstr "Corps" msgid "Reports" msgstr "Rapports" msgid "Attachments" msgstr "Pièces jointes" msgid "Files" msgstr "Fichiers" msgid "Send" msgstr "Envoyer" msgid "Select File" msgstr "Choisir un fichier" msgid "Remove File" msgstr "Supprimer le fichier" msgid "Select" msgstr "Sélectionner" msgid "Add..." msgstr "Ajouter..." msgid "Preview" msgstr "Aperçu" msgid "Previous" msgstr "Précédent" msgid "Next" msgstr "Suivant" #, python-format msgid "Attachment (%s)" msgstr "Attachement (%s)" #, python-format msgid "Note (%d/%d)" msgstr "Note (%d/%d)" msgid "You have to select one record." msgstr "Vous devez sélectionner un enregistrement." msgid "Are you sure to remove this record?" msgstr "Êtes-vous sûr de vouloir supprimer cet enregistrement ?" msgid "Are you sure to remove those records?" msgstr "Êtes-vous sûr de vouloir supprimer ces enregistrements ?" msgid "Records not removed." msgstr "Enregistrements non supprimés." msgid "Records removed." msgstr "Enregistrements supprimés." msgid "Working now on the duplicated record(s)." msgstr "Sur les enregistrement(s) dupliqué(s) maintenant." msgid "Record saved." msgstr "Entrée enregistrée." msgid "" "This record has been modified\n" "do you want to save it?" msgstr "" "Cet entrée a été modifiée\n" "voulez-vous l'enregistrer ?" msgid "Launch action" msgstr "Lancer une action" msgid "Relate" msgstr "Relation" msgid "Open related records" msgstr "Ouvrir les enregistrements liés" msgid "Report" msgstr "Rapport" msgid "Open report" msgstr "Ouvrir un rapport" msgid "Print" msgstr "Imprimer" msgid "Print report" msgstr "Imprimer un rapport" msgid "_Copy URL" msgstr "_Copier l'URL" msgid "Copy URL into clipboard" msgstr "Copier l'URL dans le presse-papiers" msgid "Unknown" msgstr "Inconnu" msgid "Limit" msgstr "Limite" msgid "Search Limit Settings" msgstr "Limite de recherche" msgid "Limit:" msgstr "Limite :" #, python-format msgid "Logs (%s)" msgstr "Journaux (%s)" msgid "Model:" msgstr "Modèle :" msgid "ID:" msgstr "ID :" msgid "Created by:" msgstr "Crée par :" msgid "Created at:" msgstr "Crée le :" msgid "Last Modified by:" msgstr "Dernière modification par :" msgid "Last Modified at:" msgstr "Dernière modification à :" #, python-format msgid "Notes (%s)" msgstr "Notes (%s)" msgid "Edit User Preferences" msgstr "Éditer les préférences utilisateur" msgid "Preference" msgstr "Préférence" msgid "Revision" msgstr "Révision" msgid "Select a revision" msgstr "Sélectionner une révision" msgid "Revision:" msgstr "Révision :" msgid "_Switch View" msgstr "_Changer de vue" msgid "Switch View" msgstr "Changer de vue" msgid "_Previous" msgstr "_Précédent" msgid "Previous Record" msgstr "Enregistrement précédent" msgid "_Next" msgstr "Sui_vant" msgid "Next Record" msgstr "Enregistrement suivant" msgid "_Search" msgstr "_Recherche" msgid "_New" msgstr "_Nouveau" msgid "Create a new record" msgstr "Créer un nouvel enregistrement" msgid "_Save" msgstr "Enregi_strer" msgid "Save this record" msgstr "Enregistrer cette entrée" msgid "_Reload/Undo" msgstr "_Recharger/Annuler" msgid "Reload/Undo" msgstr "Recharger/Annuler" msgid "_Duplicate" msgstr "_Dupliquer" msgid "_Delete..." msgstr "Su_pprimer..." msgid "View _Logs..." msgstr "Voir les _journaux..." msgid "Show revisions..." msgstr "Afficher les révisions..." msgid "A_ttachments..." msgstr "_Pièce jointes..." msgid "Add an attachment to the record" msgstr "Ajouter un attachement à l'enregistrement" msgid "_Notes..." msgstr "_Notes..." msgid "Add a note to the record" msgstr "Ajouter une note à l'enregistrement" msgid "_Actions..." msgstr "_Actions..." msgid "_Relate..." msgstr "_Relation..." msgid "_Report..." msgstr "_Rapport..." msgid "_Print..." msgstr "_Imprimer..." msgid "_E-Mail..." msgstr "_E-mail..." msgid "Send an e-mail using the record" msgstr "Envoyer un e-mail en utilisant l'enregistrement" msgid "_Export Data..." msgstr "_Export de données..." msgid "_Import Data..." msgstr "_Import de données..." msgid "Copy _URL..." msgstr "Copier l'_URL..." msgid "_Close Tab" msgstr "_Fermer l'onglet" msgid "All fields" msgstr "Tout les champs" msgid "_Add" msgstr "_Ajouter" msgid "_Remove" msgstr "_Enlever" msgid "_Clear" msgstr "_Effacer" msgid "Fields selected" msgstr "Champs sélectionnés" msgid "CSV Parameters" msgstr "Paramètres CSV" msgid "Delimiter:" msgstr "Délimiteur :" msgid "Quote char:" msgstr "Caractère de guillemet :" msgid "Encoding:" msgstr "Codage :" msgid "Use locale format" msgstr "Utiliser le format local" msgid "Field name" msgstr "Nom du champ" #, python-format msgid "CSV Export: %s" msgstr "Export CSV : %s" msgid "_Save Export" msgstr "Enregi_strer l'export" msgid "_URL Export" msgstr "_URL d'exportation" msgid "_Delete Export" msgstr "_Supprimer l'exportation" msgid "Predefined exports" msgstr "Exportations prédéfinies" msgid "Name" msgstr "Nom" msgid "Open" msgstr "Ouvrir" msgid "Save" msgstr "Enregistrer" msgid "Listed Records" msgstr "Enregistrements listés" msgid "Selected Records" msgstr "Enregistrements sélectionnés" msgid "Ignore search limit" msgstr "Ignorer la limite de recherche" msgid "Add field names" msgstr "Ajouter les noms des champs" #, python-format msgid "%s (string)" msgstr "%s (chaîne)" #, python-format msgid "%s (model name)" msgstr "%s (nom du modèle)" #, python-format msgid "%s/Record Name" msgstr "%s/Nom de l'enregistrement" msgid "What is the name of this export?" msgstr "Quel est le nom de cet export ?" #, python-format msgid "Override '%s' definition?" msgstr "Surcharger la définition « %s » ?" #, python-format msgid "%d record saved." msgstr "%d entrée enregistrée." #, python-format msgid "%d records saved." msgstr "%d entrées enregistrées." msgid "Export failed" msgstr "L'export a échoué" msgid "Link" msgstr "Lien" msgid "Delete" msgstr "Supprimer" msgid "Discard changes" msgstr "Annuler les modifications" msgid "Save and New" msgstr "Enregistrer et nouveau" msgid "Add and New" msgstr "Ajouter et nouveau" msgid "Add" msgstr "Ajouter" msgid "Apply changes" msgstr "Appliquer les modifications" msgid "Switch" msgstr "Basculer" msgid "Remove " msgstr "Supprimer " msgid "Create a new record " msgstr "Créer un nouvel enregistrement" msgid "Delete selected record " msgstr "Supprimer l'enregistrement sélectionné" msgid "Undelete selected record " msgstr "Restaurer l'enregistrement sélectionné" #, python-format msgid "CSV Import: %s" msgstr "Import CSV : %s" msgid "_Auto-Detect" msgstr "Détection _automatique" msgid "File to Import:" msgstr "Fichier à importer :" msgid "Open..." msgstr "Ouvrir..." msgid "Lines to Skip:" msgstr "Lignes à ignorer :" msgid "You must select an import file first." msgstr "Vous devez d'abord sélectionner un fichier d'import." msgid "Detection failed" msgstr "La détection a échoué" #, python-format msgid "Unknown column header \"%s\"" msgstr "En-tête de colonne inconnu « %s »" msgid "Error" msgstr "Erreur" msgid "Import failed" msgstr "L'import a échoué" #, python-format msgid "%d record imported." msgstr "%d enregistrement importé." #, python-format msgid "%d records imported." msgstr "%d enregistrements importés." msgid "Search" msgstr "Rechercher" msgid "New" msgstr "Nouveau" #, python-format msgid "Search %s" msgstr "Rechercher %s" msgid "Wizard" msgstr "Assistant" msgid "ID" msgstr "ID" msgid "Created by" msgstr "Créé par" msgid "Created at" msgstr "Crée le" msgid "Edited by" msgstr "Édité par" msgid "Edited at" msgstr "Édité le" msgid "Unable to set view tree state" msgstr "Impossible de définir l'état de la vue arbre" #, python-format msgid "\"%s\" is not valid according to its domain." msgstr "« %s » n'est pas valide selon son domaine." #, python-format msgid "\"%s\" is required." msgstr "« %s » est requis." #, python-format msgid "The values of \"%s\" are not valid." msgstr "Les valeurs de « %s » ne sont pas valides." msgid "Pre-validation" msgstr "Pré-validation" msgid ":" msgstr " :" msgid "Scan" msgstr "Scanner" msgid "Image Size" msgstr "Taille de l'image" msgid "Width:" msgstr "Largeur :" msgid "Height:" msgstr "Hauteur :" msgid "PNG image (*.png)" msgstr "image PNG (*.png)" msgid "Save As" msgstr "Enregistrer sous" msgid "Image size too large." msgstr "Taille de l'image trop grande." msgid "Copy" msgstr "Copier" msgid "Paste" msgstr "Coller" msgid ".." msgstr ".." msgid "Open filters" msgstr "Ouvrir les filtres" msgid "Show bookmarks of filters" msgstr "Montrer les marques-pages des filtres" msgid "Remove this bookmark" msgstr "Supprimer ce marque-page" msgid "Bookmark this filter" msgstr "Sauver ce filtre" msgid "Show active records" msgstr "Montrer les enregistrements actifs" msgid "Show inactive records" msgstr "Montrer les enregistrements inactifs" msgid "Bookmark Name:" msgstr "Nom du marque-page :" msgid "Find" msgstr "Chercher" msgid "Today" msgstr "Aujourd'hui" msgid "go back" msgstr "revenir" msgid "go forward" msgstr "avancer" msgid "previous year" msgstr "année précédente" msgid "next year" msgstr "année suivante" msgid "Day" msgstr "Jour" msgid "Week" msgstr "Semaine" msgid "Month" msgstr "Mois" msgid "Select..." msgstr "Sélectionner..." msgid "Clear" msgstr "Effacer" msgid "All files" msgstr "Tous les fichiers" msgid "Show plain text" msgstr "Montrer en text clair" msgid "Add value" msgstr "Ajouter une valeur" #, python-format msgid "Remove \"%s\"" msgstr "Supprimer « %s »" msgid "Images" msgstr "Images" msgid "Add existing record" msgstr "Ajouter un enregistrement existant" msgid "Remove selected record" msgstr "Supprimer l'enregistrement sélectionné" msgid "Open the record " msgstr "Ouvrir l'enregistrement " msgid "Clear the field " msgstr "Effacer le champ " msgid "Search a record " msgstr "Chercher un enregistrement" msgid "Edit selected record" msgstr "Éditer l'enregistrement sélectionné" msgid "Delete selected record" msgstr "Supprimer l'enregistrement sélectionné" #, python-format msgid "%s%%" msgstr "%s%%" msgid "Choose a language" msgstr "Choisir une langue" msgid "Translation" msgstr "Traduction" msgid "Edit" msgstr "Éditer" msgid "Fuzzy" msgstr "Floue" msgid "You need to save the record before adding translations." msgstr "Vous devez enregistrer l'entrée avant d'ajouter des traductions." msgid "No other language available." msgstr "Pas d'autre langue disponible." msgid "#ERROR" msgstr "#ERREUR" msgid "Translate view" msgstr "Traduire la vue" ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1745680285.1241102 tryton-7.0.24/tryton/data/locale/hu/0000755000175000017500000000000015003173635015401 5ustar00cedced././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1745680285.1641107 tryton-7.0.24/tryton/data/locale/hu/LC_MESSAGES/0000755000175000017500000000000015003173635017166 5ustar00cedced././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1745680279.0 tryton-7.0.24/tryton/data/locale/hu/LC_MESSAGES/tryton.mo0000644000175000017500000004141115003173627021064 0ustar00cedcedH\ * &2ADHJ\s 3GW gq x#% ,=LQV[j&    %/ ANVl  % < G S_ q { ) 2<AWl t ~    #(/5<DIeknrx #   />D FPo      ! -8ADa r   2;MSe t      $2GX _i z         & + 3 > N _ y       ! !!/!K! \!f!k! !!!!!!! "" " &"2";" @"M"I\"!"5"8"7#;# A# M#X#i#q# ##### ### $$ 0$ ;$E$ K$Y$ ^$$$($ $$$$%$7%F% V%b% g%t%|% %% % % %%%%% % % % & && '&2& 8&E& M& Z&f&h&y& {&&$&&&<& ' 7'X'Z'\'^'5(#)A)V)j)) ))))))))**/* G* S*_* h*s* |*#****+$+ 3+)A+k+{++++ +++, , ),5,H,`,x,,,,, ,,, -- --7-F-`-s--&- ---.,.L.e.%.!... ./ /,/(8> a> m>$x>>>>>>??? =?J?&Q? x???"?)?=? @ %@ 3@@@Y@a@~@@ @ @ @@@@ @ AA A,ABA`AuAAA AAAAAA%AB.!BPB,RBBBB"BCCC"%s" is not valid according to its domain."%s" is required.%d record imported.%d record saved.%d records imported.%d records saved.%s (string)%s/Record Name.....:All fieldsFields selectedPredefined exportsCreate...Search...A new version is available!A_ttachments...About...ActionActions...AddAdd a note to the recordAdd an attachment to the recordAdd existing recordAdd field namesAdd new profileAdd valueAdd...All filesAlways ignore this warning.Application ErrorApplication ShortcutsAre you sure to remove this record?Are you sure to remove those records?Attachment (%s)AttachmentsAttachments (%s)Attachments...Bcc:BodyBoldBookmark Name:Bookmark this filterCSV Export: %sCSV Import: %sCSV ParametersC_onnectCancelCancel connection to the Tryton serverCancel savingCc:Check VersionClearClear the field CloseClose TabCollapse all rowsCollapse rowCompareConcurrency ExceptionConnect the Tryton serverCopyCopy URL into clipboardCopy _URL...Copy selected textCould not connect to the server.Create a new recordCreate a new record Create new relationCreate/Select new lineCreated atCreated at:Created by:Cut selected textDatabase:Date FormatDefaultDeleteDelete selected recordDelete selected record Delimiter:DigitsDisplay formatDisplayed date formatDisplayed valueDo you want to proceed?DownloadE-Mail...EditEdit User PreferencesEdit selected recordEdit...Edited atEdited byEdition ShortcutsEncoding:ErrorExpand all rowsExpand rowExpand/CollapseFalseFavoritesFetching databases listField nameFile to Import:FilesFindFormFormatFuzzyGlobalHeight:HelpHost / Database informationHost:IDID:IconsIgnore search limitImage SizeImagesIncompatible version of the server.ItalicKeyboard Shortcuts...Launch actionLimitLimit:Lines to Skip:LinkList EntriesList/Tree ShortcutsListed RecordsLoginMManage...Mark line for deletion/removalMark line for removalModel:Move CursorMove downMove down of one pageMove leftMove rightMove to bottomMove to parentMove to topMove upMove up of one pageNameNewNextNext RecordNext entryNext tabNoNo other language available.No result found.Note (%d/%d)Notes...OKOpenOpen filtersOpen related recordsOpen relationOpen reportOpen the calendarOpen the record Open...Open/Search relationOptionsOverride '%s' definition?PDA ModePNG image (*.png)PastePaste copied textPre-validationPreferencePreferencesPreferences...PreviewPreviousPrevious RecordPrevious entryPrevious tabPrintPrint reportPrint...ProfileProfile EditorProfile:QuitQuote char:Record saved.Records not removed.Records removed.RelateRelate...Relation EntriesReload/UndoRemove "%s"Remove Remove FileRemove selected profileRemove selected recordRemove this bookmarkReportReport BugReport...ReportsRevisionRevision:SaveSave AsSave As...Save Tree StateSave this recordSave your current versionSearchSearch %sSearch Limit SettingsSearch Limit...Search a record Search menuSee the modified versionSelectSelect a revisionSelect allSelect parentSelect your actionSelect/Activate current rowSelected RecordsSelectionSendSend an e-mail using the recordShortcutsShow active recordsShow bookmarks of filtersShow inactive recordsShow plain textShow revisions...Spell CheckingSubject:SwitchSwitch ViewSwitch viewTemplateTextText EntriesText and IconsThe following action requires to close all tabs. Do you want to continue?The values of "%s" are not valid.This record has been modified do you want to save it?This record has been modified while you were editing it.To:TodayToggle menuToggle rowToggle selectionToolbarTranslate viewTranslationTrueUnable to set locale %sUnable to set view tree stateUndelete selected record UnderlineUnknownUnmark line for deletionUnselect allUse locale formatUser name:Username:ValueView _Logs...WeekWhat is the name of this export?Width:WizardWorking now on the duplicated record(s).Write AnywayYYesYou have to select one record.You must select an import file first.You need to save the record before adding translations.Your selection:_Actions..._Add_Auto-Detect_Cancel_Clear_Close Tab_Delete Export_Delete..._Duplicate_E-Mail..._Export Data..._Import Data..._New_Next_Notes..._Previous_Print..._Relate..._Reload/Undo_Remove_Report..._Save_Save Export_Search_Switch View_URL Exportddevelopment modehlogging everything at INFO levelmmodularity, scalability and securitysspecify alternate config filespecify the log level: DEBUG, INFO, WARNING, ERROR, CRITICALspecify the login userspecify the server hostname:porttwyProject-Id-Version: PROJECT VERSION Report-Msgid-Bugs-To: EMAIL@ADDRESS POT-Creation-Date: 2025-04-26 17:11+0200 PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE Last-Translator: FULL NAME Language: hu Language-Team: hu Plural-Forms: nplurals=1; plural=0; MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: 8bit Generated-By: Babel 2.17.0 A(z) „%s” mezőben helytelen értéket adott meg.A(z) „%s” mezőt meg kell adni.%d rekord betöltve.%d rekord elmentve.%d rekord betöltve.%d rekord elmentve.%s (string)%s/Rekord neve.....:Összes mezőKiválasztott mezőkElmentett oszloplistákÚj rekord...Keresés...Új verzió elérhető!Mellékle_tNévjegy...MűveletMűveletekHozzáadJegyzet hozzáadása a rekordhozMelléklet hozzáadása a rekordhozMeglévő rekord hozzáadásaMezőnevek hozzáadásaÚj profil hozzáadásaÉrték hozzáadásaHozzáadás...Összes fájlNe mutassa többet ezt a figyelmeztetéstAlkalmazáshibaÁltalános gyorsbillentyűkBiztos törölni szeretné?Biztos törölni szeretné?Melléklet(%s)MellékletekMellékletek (%s)Mellékletek...Rejtett másolat:TartalomFélkövérKönyvjelző neve:Ez a szűrő elmentéseExportálás CSV-be: %sImportálás CSV-ből: %sCSV paraméterekBejelentkezésMégseBejelentkezőablak bezárásaNe mentse elMásolatot kap:Verzió ellenőrzéseTörlésA mező kiürítéseBezárásLap bezárásaMinden sor összecsukásaSor összecsukásaÖsszehasonlításEgyidejű módosítási hibaKapcsolódás a Tryton kiszolgálóhozMásolásURL vágólapra másolása_URL másolása...Kijelölt szöveg másolásaNem sikerült csatlakozni a kiszolgálóhoz.Új rekord létrehozásaÚj rekord létrehozása Új kapcsolódó rekord létrehozásaÚj/meglévő rekord hozzáadásaLétrehozás dátumaLétrehozás dátuma:Létrehozta:Kijelölt szöveg kivágásaAdatbázis:DátumformátumAlapértelmezettTörölKiválasztott rekord törléseKiválasztott rekord törlése Elválasztójel:TizedesjegyekMegjelenítési formátumMutatott dátumformátumMegjelenített értékAkarja folyatni?LetöltésE-Mail...MódosításFelhasználói beállítások szerkesztéseKiválasztott rekord szerkesztéseMódosítás…Módosítás dátumaMódosítottaSzerkesztés gyorsbillentyűiKódolás:HibaMinden sor felnyitásaSor felnyitásaFelnyitás/összecsukásHamisKönyvjelzőkAdatbázislista lekéréseMezőnévImportadatok:FájlokKeresésŰrlapFormátumPontatlanA teljes alkalmazásban érvényesMagasság:SúgóKiszolgáló / adatbázis információKiszolgáló:IDID:IkonokKeresési korlát nélkülKép méreteKépekA kiszolgálónak más verziója van, mint a kliensnek.DőltGyorsbillentyűk...Művelet indításaKorlátKorlát:Átugrandó sorok:HivatkozásListamezőkLista-/fanézet gyorsbillentyűiA listán szereplő összes rekordotBejelentkezésHMódosítás...Sor törlésre/eltávolításra jelöléseSor törlésre jelöléseElem:Lépkedés a listaelemek közöttLeLe egy oldaltBalraJobbraUtolsó rekordra ugrásSzülőrekordra ugrásElső rekordra ugrásFelFel egy oldaltNévÚjKövetkezőKövetkező rekordKövetkező mezőKövetkező lapNemMásik nyelv nem elérhető.Nincs találatJegyzet (%d/%d)Jegyzetek...OKMegnyitásSzűrők megnyitásaKapcsolódó rekordok megnyitásaKapcsolódó rekord megnyitásaNyomtatvány megnyitásaNaptár megnyitásaRekord megnyitása Megnyitás...Kapcsolódó rekordok keresése/megnyitásaTestreszabás'%s' definiálásának figyelembe vétele?Tablet módPNG kép (*.png)BeillesztésKijelölt szöveg beillesztéseAdatkitöltés ellenőrzéseFelhasználói beállításokFelhasználói beállításokFelhasználó...ElőnézetElőzőElőző rekordElőző mezőElőző lapNyomtatásNyomtatásNyomtatás...ProfilProfilszerkesztőProfil:KilépésKarakterlánc-elválasztó:Rekord elmentve.A rekordok nem lettek törölve.Rekordok törölve.KapcsolatKapcsolódó elemek...KapcsolatmezőkÚjratöltés / Visszavonás„%s” eltávolításaMező eltávolításaFájl eltávolításaA kiválasztott profil törléseA kiválasztott rekord eltávolításaEz az elmentett szűrő eltávolításaNyomtatványA hiba jelentéseJelentés...NyomtatványokSzerkesztésSzerkesztés:MentésMentés máskéntMentés másként…Listák testreszabásának mentéseRekord mentéseÍrja felül a másikét az Ön verziójávalKeresés%s kereséseKeresett rekordok számának korlátozásaKeresési korlát...Rekord kereséseKeresősávA másik felhasználó által módosított változat megtekintéseKiválasztSzerkesztés kiválasztásaMind kijelöléseSzülőrekord kijelöléseVálasszon műveletetAktuális sor kijelölése/aktiválásaCsak a kiválasztott rekordokatKijelölésKüldésE-mail küldése a rekord adataivalGyorsbillentyűkAktív rekordok megjelenítéseElmentett szűrők megjelenítéseInaktív rekordok megjelenítéseMutassa meg az elrejtett szövegetFeldolgozások megtekintéseHelyesírás ellenőrzésTárgy:Nézet váltásaNézet váltásaNézet váltásaSablonSzövegSzövegmezőkSzöveg és ikonokA továbblépéshez be kell zárnia minden nyitott lapot. Biztosan tovább akar lépni?A(z) „%s” mező értékei között van érvénytelen.Módosította ezt a rekordot, el akarja menteni?Ezt a rekordot valaki más módosította az idő alatt, míg Ön dolgozott rajta.Címzett:MaOldalsáv ki/beSor felnyitás ki/beAktuális sor kijelölése ki/beEszköztárNézet lefordításaFordításIgazNem lehet beállítani a %s nyelvetA menü láthatósága nem állítható beRekord törlésének visszavonása AláhúzottIsmeretlenA törlésre jelölés visszavonásaKijelölés megszüntetéseA felhasználó nyelvénFelhasználónév:Felhasználónév:ÉrtékNap_ló megtekintése…HétMi legyen az oszloplista neve?Szélesség:WizardMost az új másolaton(okon) dolgozik.FelülírásÉIgenKi kell választania egy rekordot.Először adja meg a betöltendő fájlt.El kell menteni a rekordot mielőtt hozzáadja a fordítást.KiválasztvaMűveletek...Hozzá_adás_Automatikus felismerés_MégseÖsszes eltávolítása (_C)Lap bezárásaOszloplista törlése (_D)Törlés..._DuplázásE-mail...Adat _exportálás...Adat _importálás...Ú_j_KövetkezőJegyzetek..._ElőzőNyomtatás…Kapcsolat megnyitásaÚj_ratöltés / Visszavonás_Eltávolítás (_R)Nyomtatvány megnyitása...Menté_sOszloplista elmenté_seKere_sésNézet váltá_saExportálás _URL-jetfejlesztői módóminden INFO szintű üzenet naplózvammodularitás, skálázhatóság és biztonságsadjon meg alternatív konfigurációs fájltnaplózási szint megadása: DEBUG, INFO, WARNING, ERROR, CRITICALadja meg a felhasználónevetadja meg a kiszolgálónév:portotwhJ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1703673879.0 tryton-7.0.24/tryton/data/locale/hu/LC_MESSAGES/tryton.po0000644000175000017500000005045614543000027021067 0ustar00cedced# Translations template for tryton. # Copyright (C) 2016 Tryton # This file is distributed under the same license as the tryton project. # FIRST AUTHOR , 2016. msgid "" msgstr "Content-Type: text/plain; charset=utf-8\n" msgid "specify alternate config file" msgstr "adjon meg alternatív konfigurációs fájlt" msgid "development mode" msgstr "fejlesztői mód" msgid "logging everything at INFO level" msgstr "minden INFO szintű üzenet naplózva" msgid "specify the log level: DEBUG, INFO, WARNING, ERROR, CRITICAL" msgstr "naplózási szint megadása: DEBUG, INFO, WARNING, ERROR, CRITICAL" msgid "specify the file used to output logging information" msgstr "" msgid "specify the login user" msgstr "adja meg a felhasználónevet" msgid "specify the server hostname:port" msgstr "adja meg a kiszolgálónév:portot" msgid "disable thread usage" msgstr "" #, python-format msgid "Unable to set locale %s" msgstr "Nem lehet beállítani a %s nyelvet" #, fuzzy msgid ", " msgstr ", " msgid ",..." msgstr "" #, python-format msgid "%s (%s)" msgstr "" msgid "Select your action" msgstr "Válasszon műveletet" #, fuzzy msgid "No action defined." msgstr "Nincs megadva művelet" msgid "By: " msgstr "" msgid "Selection" msgstr "Kijelölés" msgid "Cancel" msgstr "Mégse" msgid "OK" msgstr "OK" msgid "Your selection:" msgstr "Kiválasztva" msgid "Save As..." msgstr "Mentés másként…" msgid "Do you want to proceed?" msgstr "Akarja folyatni?" msgid "Always ignore this warning." msgstr "Ne mutassa többet ezt a figyelmeztetést" msgid "No" msgstr "Nem" msgid "Yes" msgstr "Igen" msgid "Concurrency Exception" msgstr "Egyidejű módosítási hiba" msgid "This record has been modified while you were editing it." msgstr "" "Ezt a rekordot valaki más módosította az idő alatt, míg Ön dolgozott rajta." msgid "Cancel saving" msgstr "Ne mentse el" msgid "Compare" msgstr "Összehasonlítás" msgid "See the modified version" msgstr "A másik felhasználó által módosított változat megtekintése" msgid "Write Anyway" msgstr "Felülírás" msgid "Save your current version" msgstr "Írja felül a másikét az Ön verziójával" #, fuzzy, python-format msgid "Compare: %s" msgstr "Összehasonlítás: %s" msgid "Close" msgstr "Bezárás" msgid "Application Error" msgstr "Alkalmazáshiba" msgid "Report Bug" msgstr "A hiba jelentése" #, python-format msgid "Check URL: %s" msgstr "" msgid "Unable to check for new version." msgstr "" msgid "A new version is available!" msgstr "Új verzió elérhető!" msgid "Download" msgstr "Letöltés" #, fuzzy msgid "Could not get a session." msgstr "Nem sikerült csatlakozni a kiszolgálóhoz." msgid "Too many requests. Try again later." msgstr "" #, fuzzy msgid "Not found." msgstr "Nincs találat" #, fuzzy msgid "Not Found." msgstr "Nincs találat" msgid "..." msgstr "..." msgid "Search..." msgstr "Keresés..." msgid "Create..." msgstr "Új rekord..." #, fuzzy, python-format msgid "Create \"%s\"..." msgstr "\"%s\" létrehozása..." msgid "Value" msgstr "Érték" msgid "Displayed value" msgstr "Megjelenített érték" msgid "Format" msgstr "Formátum" msgid "Display format" msgstr "Megjelenítési formátum" msgid "Open the calendar" msgstr "Naptár megnyitása" msgid "Date Format" msgstr "Dátumformátum" msgid "Displayed date format" msgstr "Mutatott dátumformátum" msgid "y" msgstr "J" msgid "True" msgstr "Igaz" msgid "t" msgstr "w" msgid "False" msgstr "Hamis" msgid "Digits" msgstr "Tizedesjegyek" msgid "The number of decimal" msgstr "" msgid "Template" msgstr "Sablon" msgid "Edit..." msgstr "Módosítás…" #, fuzzy msgid "View Logs..." msgstr "Nap_ló megtekintése…" msgid "Attachments..." msgstr "Mellékletek..." msgid "Notes..." msgstr "Jegyzetek..." msgid "Actions..." msgstr "Műveletek" msgid "Relate..." msgstr "Kapcsolódó elemek..." msgid "Report..." msgstr "Jelentés..." msgid "Print..." msgstr "Nyomtatás..." msgid "E-Mail..." msgstr "E-Mail..." msgid "Bold" msgstr "Félkövér" msgid "Italic" msgstr "Dőlt" msgid "Underline" msgstr "Aláhúzott" msgid "Align Left" msgstr "" msgid "Align Center" msgstr "" msgid "Align Right" msgstr "" msgid "Justify" msgstr "" msgid "Foreground Color" msgstr "" msgid "Select a color" msgstr "" msgid "Y" msgstr "É" msgid "M" msgstr "H" msgid "w" msgstr "h" msgid "d" msgstr "t" msgid "h" msgstr "ó" msgid "m" msgstr "m" msgid "s" msgstr "s" msgid "Preferences..." msgstr "Felhasználó..." msgid "Toolbar" msgstr "Eszköztár" msgid "Default" msgstr "Alapértelmezett" msgid "Text and Icons" msgstr "Szöveg és ikonok" msgid "Text" msgstr "Szöveg" msgid "Icons" msgstr "Ikonok" msgid "Form" msgstr "Űrlap" msgid "Save Column Width" msgstr "" msgid "Save Tree State" msgstr "Listák testreszabásának mentése" msgid "Spell Checking" msgstr "Helyesírás ellenőrzés" msgid "Play Sound for Code Scanner" msgstr "" msgid "PDA Mode" msgstr "Tablet mód" msgid "Search Limit..." msgstr "Keresési korlát..." msgid "Check Version" msgstr "Verzió ellenőrzése" msgid "Options" msgstr "Testreszabás" #, fuzzy msgid "Documentation..." msgstr "Műveletek" msgid "Keyboard Shortcuts..." msgstr "Gyorsbillentyűk..." msgid "About..." msgstr "Névjegy..." msgid "Help" msgstr "Súgó" msgid "No result found." msgstr "Nincs találat" msgid "Favorites" msgstr "Könyvjelzők" msgid "Manage..." msgstr "Módosítás..." msgid "Action" msgstr "Művelet" msgid "" "The following action requires to close all tabs.\n" "Do you want to continue?" msgstr "" "A továbblépéshez be kell zárnia minden nyitott lapot.\n" "Biztosan tovább akar lépni?" msgid "Application Shortcuts" msgstr "Általános gyorsbillentyűk" msgid "Global" msgstr "A teljes alkalmazásban érvényes" msgid "Preferences" msgstr "Felhasználói beállítások" msgid "Search menu" msgstr "Keresősáv" msgid "Toggle menu" msgstr "Oldalsáv ki/be" msgid "Previous tab" msgstr "Előző lap" msgid "Next tab" msgstr "Következő lap" msgid "Shortcuts" msgstr "Gyorsbillentyűk" msgid "Quit" msgstr "Kilépés" msgid "Edition Shortcuts" msgstr "Szerkesztés gyorsbillentyűi" msgid "Text Entries" msgstr "Szövegmezők" msgid "Cut selected text" msgstr "Kijelölt szöveg kivágása" msgid "Copy selected text" msgstr "Kijelölt szöveg másolása" msgid "Paste copied text" msgstr "Kijelölt szöveg beillesztése" msgid "Next entry" msgstr "Következő mező" msgid "Previous entry" msgstr "Előző mező" msgid "Relation Entries" msgstr "Kapcsolatmezők" msgid "Create new relation" msgstr "Új kapcsolódó rekord létrehozása" msgid "Open/Search relation" msgstr "Kapcsolódó rekordok keresése/megnyitása" msgid "List Entries" msgstr "Listamezők" msgid "Switch view" msgstr "Nézet váltása" msgid "Create/Select new line" msgstr "Új/meglévő rekord hozzáadása" msgid "Open relation" msgstr "Kapcsolódó rekord megnyitása" msgid "Mark line for deletion/removal" msgstr "Sor törlésre/eltávolításra jelölése" msgid "Mark line for removal" msgstr "Sor törlésre jelölése" msgid "Unmark line for deletion" msgstr "A törlésre jelölés visszavonása" msgid "List/Tree Shortcuts" msgstr "Lista-/fanézet gyorsbillentyűi" msgid "Move Cursor" msgstr "Lépkedés a listaelemek között" msgid "Move right" msgstr "Jobbra" msgid "Move left" msgstr "Balra" msgid "Move up" msgstr "Fel" msgid "Move down" msgstr "Le" msgid "Move up of one page" msgstr "Fel egy oldalt" msgid "Move down of one page" msgstr "Le egy oldalt" msgid "Move to top" msgstr "Első rekordra ugrás" msgid "Move to bottom" msgstr "Utolsó rekordra ugrás" msgid "Move to parent" msgstr "Szülőrekordra ugrás" #, fuzzy msgid "Edition" msgstr "Módosítás" #, fuzzy msgid "Copy selected rows" msgstr "Kijelölt szöveg másolása" msgid "Insert copied rows" msgstr "" msgid "Select all" msgstr "Mind kijelölése" msgid "Unselect all" msgstr "Kijelölés megszüntetése" msgid "Select parent" msgstr "Szülőrekord kijelölése" msgid "Select/Activate current row" msgstr "Aktuális sor kijelölése/aktiválása" msgid "Toggle selection" msgstr "Aktuális sor kijelölése ki/be" msgid "Expand/Collapse" msgstr "Felnyitás/összecsukás" msgid "Expand row" msgstr "Sor felnyitása" msgid "Collapse row" msgstr "Sor összecsukása" msgid "Toggle row" msgstr "Sor felnyitás ki/be" msgid "Collapse all rows" msgstr "Minden sor összecsukása" msgid "Expand all rows" msgstr "Minden sor felnyitása" msgid "Close Tab" msgstr "Lap bezárása" msgid "modularity, scalability and security" msgstr "modularitás, skálázhatóság és biztonság" msgid "translator-credits" msgstr "" #, python-format msgid "Attachments (%s)" msgstr "Mellékletek (%s)" msgid "Code Scanner" msgstr "" msgid "Code" msgstr "" msgid "Profile Editor" msgstr "Profilszerkesztő" msgid "Profile" msgstr "Profil" msgid "Add new profile" msgstr "Új profil hozzáadása" msgid "Remove selected profile" msgstr "A kiválasztott profil törlése" msgid "Host:" msgstr "Kiszolgáló:" msgid "Database:" msgstr "Adatbázis:" msgid "Fetching databases list" msgstr "Adatbázislista lekérése" msgid "Username:" msgstr "Felhasználónév:" msgid "Incompatible version of the server." msgstr "A kiszolgálónak más verziója van, mint a kliensnek." msgid "Could not connect to the server." msgstr "Nem sikerült csatlakozni a kiszolgálóhoz." msgid "Login" msgstr "Bejelentkezés" msgid "_Cancel" msgstr "_Mégse" msgid "Cancel connection to the Tryton server" msgstr "Bejelentkezőablak bezárása" msgid "C_onnect" msgstr "Bejelentkezés" msgid "Connect the Tryton server" msgstr "Kapcsolódás a Tryton kiszolgálóhoz" msgid "Profile:" msgstr "Profil:" msgid "Host / Database information" msgstr "Kiszolgáló / adatbázis információ" msgid "User name:" msgstr "Felhasználónév:" #, fuzzy msgid "Unable to complete email entry" msgstr "Nem lehet beállítani a csoportot: %s" #, fuzzy, python-format msgid "E-mail %s" msgstr "E-mail %s" msgid "To:" msgstr "Címzett:" msgid "Cc:" msgstr "Másolatot kap:" msgid "Bcc:" msgstr "Rejtett másolat:" msgid "Subject:" msgstr "Tárgy:" msgid "Body" msgstr "Tartalom" msgid "Reports" msgstr "Nyomtatványok" msgid "Attachments" msgstr "Mellékletek" msgid "Files" msgstr "Fájlok" msgid "Send" msgstr "Küldés" #, fuzzy msgid "Select File" msgstr "Mind kijelölése" msgid "Remove File" msgstr "Fájl eltávolítása" msgid "Select" msgstr "Kiválaszt" msgid "Add..." msgstr "Hozzáadás..." msgid "Preview" msgstr "Előnézet" msgid "Previous" msgstr "Előző" msgid "Next" msgstr "Következő" #, python-format msgid "Attachment (%s)" msgstr "Melléklet(%s)" #, python-format msgid "Note (%d/%d)" msgstr "Jegyzet (%d/%d)" msgid "You have to select one record." msgstr "Ki kell választania egy rekordot." msgid "Are you sure to remove this record?" msgstr "Biztos törölni szeretné?" msgid "Are you sure to remove those records?" msgstr "Biztos törölni szeretné?" msgid "Records not removed." msgstr "A rekordok nem lettek törölve." msgid "Records removed." msgstr "Rekordok törölve." msgid "Working now on the duplicated record(s)." msgstr "Most az új másolaton(okon) dolgozik." msgid "Record saved." msgstr "Rekord elmentve." msgid "" "This record has been modified\n" "do you want to save it?" msgstr "" "Módosította ezt a rekordot,\n" "el akarja menteni?" msgid "Launch action" msgstr "Művelet indítása" msgid "Relate" msgstr "Kapcsolat" msgid "Open related records" msgstr "Kapcsolódó rekordok megnyitása" msgid "Report" msgstr "Nyomtatvány" msgid "Open report" msgstr "Nyomtatvány megnyitása" msgid "Print" msgstr "Nyomtatás" msgid "Print report" msgstr "Nyomtatás" msgid "_Copy URL" msgstr "" msgid "Copy URL into clipboard" msgstr "URL vágólapra másolása" msgid "Unknown" msgstr "Ismeretlen" msgid "Limit" msgstr "Korlát" msgid "Search Limit Settings" msgstr "Keresett rekordok számának korlátozása" msgid "Limit:" msgstr "Korlát:" #, python-format msgid "Logs (%s)" msgstr "" msgid "Model:" msgstr "Elem:" msgid "ID:" msgstr "ID:" msgid "Created by:" msgstr "Létrehozta:" msgid "Created at:" msgstr "Létrehozás dátuma:" #, fuzzy msgid "Last Modified by:" msgstr "Utolsó módosítás" #, fuzzy msgid "Last Modified at:" msgstr "Módosítás dátuma " #, python-format msgid "Notes (%s)" msgstr "" msgid "Edit User Preferences" msgstr "Felhasználói beállítások szerkesztése" msgid "Preference" msgstr "Felhasználói beállítások" msgid "Revision" msgstr "Szerkesztés" msgid "Select a revision" msgstr "Szerkesztés kiválasztása" msgid "Revision:" msgstr "Szerkesztés:" msgid "_Switch View" msgstr "Nézet váltá_sa" msgid "Switch View" msgstr "Nézet váltása" msgid "_Previous" msgstr "_Előző" msgid "Previous Record" msgstr "Előző rekord" msgid "_Next" msgstr "_Következő" msgid "Next Record" msgstr "Következő rekord" msgid "_Search" msgstr "Kere_sés" msgid "_New" msgstr "Ú_j" msgid "Create a new record" msgstr "Új rekord létrehozása" msgid "_Save" msgstr "Menté_s" msgid "Save this record" msgstr "Rekord mentése" msgid "_Reload/Undo" msgstr "Új_ratöltés / Visszavonás" msgid "Reload/Undo" msgstr "Újratöltés / Visszavonás" msgid "_Duplicate" msgstr "_Duplázás" msgid "_Delete..." msgstr "Törlés..." msgid "View _Logs..." msgstr "Nap_ló megtekintése…" msgid "Show revisions..." msgstr "Feldolgozások megtekintése" msgid "A_ttachments..." msgstr "Mellékle_t" msgid "Add an attachment to the record" msgstr "Melléklet hozzáadása a rekordhoz" msgid "_Notes..." msgstr "Jegyzetek..." msgid "Add a note to the record" msgstr "Jegyzet hozzáadása a rekordhoz" msgid "_Actions..." msgstr "Műveletek..." msgid "_Relate..." msgstr "Kapcsolat megnyitása" msgid "_Report..." msgstr "Nyomtatvány megnyitása..." msgid "_Print..." msgstr "Nyomtatás…" msgid "_E-Mail..." msgstr "E-mail..." msgid "Send an e-mail using the record" msgstr "E-mail küldése a rekord adataival" msgid "_Export Data..." msgstr "Adat _exportálás..." msgid "_Import Data..." msgstr "Adat _importálás..." msgid "Copy _URL..." msgstr "_URL másolása..." msgid "_Close Tab" msgstr "Lap bezárása" msgid "All fields" msgstr "Összes mező" msgid "_Add" msgstr "Hozzá_adás" msgid "_Remove" msgstr "_Eltávolítás (_R)" msgid "_Clear" msgstr "Összes eltávolítása (_C)" msgid "Fields selected" msgstr "Kiválasztott mezők" msgid "CSV Parameters" msgstr "CSV paraméterek" msgid "Delimiter:" msgstr "Elválasztójel:" msgid "Quote char:" msgstr "Karakterlánc-elválasztó:" msgid "Encoding:" msgstr "Kódolás:" msgid "Use locale format" msgstr "A felhasználó nyelvén" msgid "Field name" msgstr "Mezőnév" #, python-format msgid "CSV Export: %s" msgstr "Exportálás CSV-be: %s" msgid "_Save Export" msgstr "Oszloplista elmenté_se" msgid "_URL Export" msgstr "Exportálás _URL-je" msgid "_Delete Export" msgstr "Oszloplista törlése (_D)" msgid "Predefined exports" msgstr "Elmentett oszloplisták" msgid "Name" msgstr "Név" msgid "Open" msgstr "Megnyitás" msgid "Save" msgstr "Mentés" msgid "Listed Records" msgstr "A listán szereplő összes rekordot" msgid "Selected Records" msgstr "Csak a kiválasztott rekordokat" msgid "Ignore search limit" msgstr "Keresési korlát nélkül" msgid "Add field names" msgstr "Mezőnevek hozzáadása" #, python-format msgid "%s (string)" msgstr "%s (string)" #, python-format msgid "%s (model name)" msgstr "" #, python-format msgid "%s/Record Name" msgstr "%s/Rekord neve" msgid "What is the name of this export?" msgstr "Mi legyen az oszloplista neve?" #, python-format msgid "Override '%s' definition?" msgstr "'%s' definiálásának figyelembe vétele?" #, python-format msgid "%d record saved." msgstr "%d rekord elmentve." #, python-format msgid "%d records saved." msgstr "%d rekord elmentve." msgid "Export failed" msgstr "" msgid "Link" msgstr "Hivatkozás" msgid "Delete" msgstr "Töröl" msgid "Discard changes" msgstr "" msgid "Save and New" msgstr "" #, fuzzy msgid "Add and New" msgstr "Érték hozzáadása" msgid "Add" msgstr "Hozzáad" msgid "Apply changes" msgstr "" msgid "Switch" msgstr "Nézet váltása" msgid "Remove " msgstr "Mező eltávolítása" msgid "Create a new record " msgstr "Új rekord létrehozása " msgid "Delete selected record " msgstr "Kiválasztott rekord törlése " msgid "Undelete selected record " msgstr "Rekord törlésének visszavonása " #, python-format msgid "CSV Import: %s" msgstr "Importálás CSV-ből: %s" msgid "_Auto-Detect" msgstr "_Automatikus felismerés" msgid "File to Import:" msgstr "Importadatok:" msgid "Open..." msgstr "Megnyitás..." msgid "Lines to Skip:" msgstr "Átugrandó sorok:" msgid "You must select an import file first." msgstr "Először adja meg a betöltendő fájlt." msgid "Detection failed" msgstr "" #, python-format msgid "Unknown column header \"%s\"" msgstr "" msgid "Error" msgstr "Hiba" msgid "Import failed" msgstr "" #, python-format msgid "%d record imported." msgstr "%d rekord betöltve." #, python-format msgid "%d records imported." msgstr "%d rekord betöltve." msgid "Search" msgstr "Keresés" msgid "New" msgstr "Új" #, python-format msgid "Search %s" msgstr "%s keresése" msgid "Wizard" msgstr "Wizard" msgid "ID" msgstr "ID" #, fuzzy msgid "Created by" msgstr "Létrehozta:" msgid "Created at" msgstr "Létrehozás dátuma" msgid "Edited by" msgstr "Módosította" msgid "Edited at" msgstr "Módosítás dátuma" msgid "Unable to set view tree state" msgstr "A menü láthatósága nem állítható be" #, python-format msgid "\"%s\" is not valid according to its domain." msgstr "A(z) „%s” mezőben helytelen értéket adott meg." #, python-format msgid "\"%s\" is required." msgstr "A(z) „%s” mezőt meg kell adni." #, python-format msgid "The values of \"%s\" are not valid." msgstr "A(z) „%s” mező értékei között van érvénytelen." msgid "Pre-validation" msgstr "Adatkitöltés ellenőrzése" msgid ":" msgstr ":" msgid "Scan" msgstr "" msgid "Image Size" msgstr "Kép mérete" msgid "Width:" msgstr "Szélesség:" msgid "Height:" msgstr "Magasság:" msgid "PNG image (*.png)" msgstr "PNG kép (*.png)" msgid "Save As" msgstr "Mentés másként" #, fuzzy msgid "Image size too large." msgstr "Kép túl nagy!" msgid "Copy" msgstr "Másolás" msgid "Paste" msgstr "Beillesztés" msgid ".." msgstr ".." msgid "Open filters" msgstr "Szűrők megnyitása" msgid "Show bookmarks of filters" msgstr "Elmentett szűrők megjelenítése" msgid "Remove this bookmark" msgstr "Ez az elmentett szűrő eltávolítása" msgid "Bookmark this filter" msgstr "Ez a szűrő elmentése" msgid "Show active records" msgstr "Aktív rekordok megjelenítése" msgid "Show inactive records" msgstr "Inaktív rekordok megjelenítése" msgid "Bookmark Name:" msgstr "Könyvjelző neve:" msgid "Find" msgstr "Keresés" msgid "Today" msgstr "Ma" msgid "go back" msgstr "" msgid "go forward" msgstr "" msgid "previous year" msgstr "" msgid "next year" msgstr "" msgid "Day" msgstr "" msgid "Week" msgstr "Hét" #, fuzzy msgid "Month" msgstr "Havi nézet" msgid "Select..." msgstr "" msgid "Clear" msgstr "Törlés" msgid "All files" msgstr "Összes fájl" msgid "Show plain text" msgstr "Mutassa meg az elrejtett szöveget" msgid "Add value" msgstr "Érték hozzáadása" #, python-format msgid "Remove \"%s\"" msgstr "„%s” eltávolítása" msgid "Images" msgstr "Képek" msgid "Add existing record" msgstr "Meglévő rekord hozzáadása" msgid "Remove selected record" msgstr "A kiválasztott rekord eltávolítása" msgid "Open the record " msgstr "Rekord megnyitása " msgid "Clear the field " msgstr "A mező kiürítése" msgid "Search a record " msgstr "Rekord keresése" msgid "Edit selected record" msgstr "Kiválasztott rekord szerkesztése" msgid "Delete selected record" msgstr "Kiválasztott rekord törlése" #, python-format msgid "%s%%" msgstr "" msgid "Choose a language" msgstr "" msgid "Translation" msgstr "Fordítás" msgid "Edit" msgstr "Módosítás" msgid "Fuzzy" msgstr "Pontatlan" msgid "You need to save the record before adding translations." msgstr "El kell menteni a rekordot mielőtt hozzáadja a fordítást." msgid "No other language available." msgstr "Másik nyelv nem elérhető." msgid "#ERROR" msgstr "" msgid "Translate view" msgstr "Nézet lefordítása" ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1745680285.1241102 tryton-7.0.24/tryton/data/locale/id/0000755000175000017500000000000015003173635015361 5ustar00cedced././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1745680285.1641107 tryton-7.0.24/tryton/data/locale/id/LC_MESSAGES/0000755000175000017500000000000015003173635017146 5ustar00cedced././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1745680279.0 tryton-7.0.24/tryton/data/locale/id/LC_MESSAGES/tryton.mo0000644000175000017500000001550715003173627021053 0ustar00cedced     # ( + 0 3 7 9 J [ w ~         * < B H P \ a         ) / 5 F N S V Z ` k #          " 1 = E Y ^ b g j }             ' 2<D MW\ do    I !S8u#   #* 1>@D T^`q y   < 5VXkmo (49<ADHJYh}     , < J Wa gr     % + 5? GRap v   !#*;DWi| #$ EUX ] jx        /A\t z R/3G{+ !   ! $ 2=?QY^`$=;=CE"%s" is required.%s (%s)%s (model name)%s (string)%s%%, ,........:Create...Search...A new version is available!ActionAddAlign CenterAlign LeftAlign RightAlways ignore this warning.Attachment (%s)AttachmentsAttachments (%s)BoldBy: CancelCancel savingCheck URL: %sCheck VersionChoose a languageClearCloseCompareCompare: %sCopyCould not connect to the server.Created atCreated at:Created byCreated by:Date FormatDeleteDisplayed valueDo you want to proceed?DownloadE-Mail...E-mail %sEditEdit...Edited atEdited byFalseFilesForeground ColorHeight:HelpIDID:IconsImage SizeImage size too large.Incompatible version of the server.ItalicJustifyLimitLinkLogs (%s)MModel:Move CursorMove downMove down of one pageMove leftMove rightMove to bottomMove to parentMove to topMove upMove up of one pageNameNewNextNoNo action defined.No other language available.Note (%d/%d)OKOpenOpen reportOpen the calendarOpen...PreviewPreviousPrintPrint reportPrint...QuitRemove Remove FileReportReport BugReport...ReportsRevisionRevision:SaveSave AsSave As...Save your current versionSee the modified versionSelectSelect FileSelect a colorSelect allSelect your actionSelect...SelectionSendTemplateTextThe following action requires to close all tabs. Do you want to continue?The values of "%s" are not valid.This record has been modified while you were editing it.To:TodayToo many requests. Try again later.TranslationTrueUnable to check for new version.UnderlineValueWeekWidth:WizardWrite AnywayYYesYour selection:_Copy URLddevelopment modego backgo forwardhlogging everything at INFO levelmnext yearprevious yearsspecify alternate config filespecify the log level: DEBUG, INFO, WARNING, ERROR, CRITICALspecify the login userspecify the server hostname:portttranslator-creditswyProject-Id-Version: PROJECT VERSION Report-Msgid-Bugs-To: EMAIL@ADDRESS POT-Creation-Date: 2025-04-26 17:11+0200 PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE Last-Translator: FULL NAME Language: id Language-Team: id Plural-Forms: nplurals=2; plural=(n != 1); MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: 8bit Generated-By: Babel 2.17.0 "%s" diperlukan.%s (%s)%s (nama model)%s (string)%s%%, ,........:Buat...Cari...Versi baru tersedia!TindakanTambahRata TengahRata KiriRata KananSelalu abaikan peringatan ini.Lampiran (%s)Lampiran-LampiranLampiran-Lampiran (%s)TebalOleh: BatalBatalkan penyimpananPeriksa URL: %sPeriksa VersiPilih bahasaBersihkanTutupBandingkanBandingkan: %sSalinTidak dapat terhubung ke server.Dibuat padaDibuat pada:Dibuat olehDibuat oleh:Format TanggalHapusNilai yang ditampilkanApakah Anda ingin melanjutkan?UnduhE-Mail...E-mail %sSuntingSunting...Disunting padaDisunting olehSalahBerkas-BerkasWarna Latar DepanTinggi:BantuanIDID:IkonUkuran GambarUkuran gambar terlalu besar.Versi server tidak sesuai.MiringSama RataBatasanTautanLog (%s)MModel:Pindahkan KursorTurunkanTurun satu halamanPindahkan ke kiriPindahkan ke kananPindahkan ke paling bawahPindahkan sebagai indukPindahkan ke paling atasNaikkanNaik satu halamanNamaBaruBerikutnyaTidakTidak ada tindakan yang ditentukan.Tidak ada bahasa lain yang tersedia.Catatan (%d/%d)OKBukaBuka LaporanBuka kalenderBuka...PratinjauSebelumnyaCetakCetak laporanCetak...KeluarHapus Hapus BerkasLaporanMelaporkan KesalahanLaporan...Laporan-LaporanRevisiRevisi:SimpanSimpan SebagaiSimpan Sebagai...Simpan versi Anda saat iniLihat versi yang diubahPilihPilih BerkasPilih satu warnaPilih semuaPilih tindakan AndaPilih...PilihanKirimTemplatTeksTindakan berikut ini perlu untuk menutup semua tab. Apakah Anda ingin melanjutkan?Nilai "%s" tidak valid.Rekaman ini telah diubah selagi Anda menyuntingnya.Kepada:Hari iniTerlalu banyak permintaan. Coba lagi nanti.TerjemahanBenarTidak dapat memeriksa versi baru.Garis BawahNilaiPekanLebar:WisayaTetap MenulisYYaPilihan Anda:_Salin URLdmode pengembangankembalimajuhmencatat semua pada level INFOmtahun berikutnyatahun sebelumnyastentukan file konfigurasi alternatiftentukan tingkatan log: DEBUG, INFO, WARNING, ERROR, CRITICALtentukan user logintentukan nama host server:porttFerrywy././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1703673879.0 tryton-7.0.24/tryton/data/locale/id/LC_MESSAGES/tryton.po0000644000175000017500000003661314543000027021046 0ustar00cedced# msgid "" msgstr "Content-Type: text/plain; charset=utf-8\n" msgid "specify alternate config file" msgstr "tentukan file konfigurasi alternatif" msgid "development mode" msgstr "mode pengembangan" msgid "logging everything at INFO level" msgstr "mencatat semua pada level INFO" msgid "specify the log level: DEBUG, INFO, WARNING, ERROR, CRITICAL" msgstr "tentukan tingkatan log: DEBUG, INFO, WARNING, ERROR, CRITICAL" msgid "specify the file used to output logging information" msgstr "" msgid "specify the login user" msgstr "tentukan user login" msgid "specify the server hostname:port" msgstr "tentukan nama host server:port" msgid "disable thread usage" msgstr "" #, python-format msgid "Unable to set locale %s" msgstr "" msgid ", " msgstr ", " msgid ",..." msgstr ",..." #, python-format msgid "%s (%s)" msgstr "%s (%s)" msgid "Select your action" msgstr "Pilih tindakan Anda" msgid "No action defined." msgstr "Tidak ada tindakan yang ditentukan." msgid "By: " msgstr "Oleh: " msgid "Selection" msgstr "Pilihan" msgid "Cancel" msgstr "Batal" msgid "OK" msgstr "OK" msgid "Your selection:" msgstr "Pilihan Anda:" msgid "Save As..." msgstr "Simpan Sebagai..." msgid "Do you want to proceed?" msgstr "Apakah Anda ingin melanjutkan?" msgid "Always ignore this warning." msgstr "Selalu abaikan peringatan ini." msgid "No" msgstr "Tidak" msgid "Yes" msgstr "Ya" msgid "Concurrency Exception" msgstr "" msgid "This record has been modified while you were editing it." msgstr "Rekaman ini telah diubah selagi Anda menyuntingnya." msgid "Cancel saving" msgstr "Batalkan penyimpanan" msgid "Compare" msgstr "Bandingkan" msgid "See the modified version" msgstr "Lihat versi yang diubah" msgid "Write Anyway" msgstr "Tetap Menulis" msgid "Save your current version" msgstr "Simpan versi Anda saat ini" #, python-format msgid "Compare: %s" msgstr "Bandingkan: %s" msgid "Close" msgstr "Tutup" msgid "Application Error" msgstr "" msgid "Report Bug" msgstr "Melaporkan Kesalahan" #, python-format msgid "Check URL: %s" msgstr "Periksa URL: %s" msgid "Unable to check for new version." msgstr "Tidak dapat memeriksa versi baru." msgid "A new version is available!" msgstr "Versi baru tersedia!" msgid "Download" msgstr "Unduh" #, fuzzy msgid "Could not get a session." msgstr "Tidak dapat terhubung ke server." msgid "Too many requests. Try again later." msgstr "Terlalu banyak permintaan. Coba lagi nanti." msgid "Not found." msgstr "" msgid "Not Found." msgstr "" msgid "..." msgstr "..." msgid "Search..." msgstr "Cari..." msgid "Create..." msgstr "Buat..." #, fuzzy, python-format msgid "Create \"%s\"..." msgstr "Buat \"%s\"..." msgid "Value" msgstr "Nilai" msgid "Displayed value" msgstr "Nilai yang ditampilkan" msgid "Format" msgstr "" msgid "Display format" msgstr "" msgid "Open the calendar" msgstr "Buka kalender" msgid "Date Format" msgstr "Format Tanggal" msgid "Displayed date format" msgstr "" msgid "y" msgstr "y" msgid "True" msgstr "Benar" msgid "t" msgstr "t" msgid "False" msgstr "Salah" msgid "Digits" msgstr "" msgid "The number of decimal" msgstr "" msgid "Template" msgstr "Templat" msgid "Edit..." msgstr "Sunting..." msgid "View Logs..." msgstr "" msgid "Attachments..." msgstr "" msgid "Notes..." msgstr "" msgid "Actions..." msgstr "" msgid "Relate..." msgstr "" msgid "Report..." msgstr "Laporan..." msgid "Print..." msgstr "Cetak..." msgid "E-Mail..." msgstr "E-Mail..." msgid "Bold" msgstr "Tebal" msgid "Italic" msgstr "Miring" msgid "Underline" msgstr "Garis Bawah" msgid "Align Left" msgstr "Rata Kiri" msgid "Align Center" msgstr "Rata Tengah" msgid "Align Right" msgstr "Rata Kanan" msgid "Justify" msgstr "Sama Rata" msgid "Foreground Color" msgstr "Warna Latar Depan" msgid "Select a color" msgstr "Pilih satu warna" msgid "Y" msgstr "Y" msgid "M" msgstr "M" msgid "w" msgstr "w" msgid "d" msgstr "d" msgid "h" msgstr "h" msgid "m" msgstr "m" msgid "s" msgstr "s" msgid "Preferences..." msgstr "" msgid "Toolbar" msgstr "" msgid "Default" msgstr "" msgid "Text and Icons" msgstr "" msgid "Text" msgstr "Teks" msgid "Icons" msgstr "Ikon" msgid "Form" msgstr "" msgid "Save Column Width" msgstr "" msgid "Save Tree State" msgstr "" msgid "Spell Checking" msgstr "" msgid "Play Sound for Code Scanner" msgstr "" msgid "PDA Mode" msgstr "" msgid "Search Limit..." msgstr "" msgid "Check Version" msgstr "Periksa Versi" msgid "Options" msgstr "" msgid "Documentation..." msgstr "" msgid "Keyboard Shortcuts..." msgstr "" msgid "About..." msgstr "" msgid "Help" msgstr "Bantuan" msgid "No result found." msgstr "" msgid "Favorites" msgstr "" msgid "Manage..." msgstr "" msgid "Action" msgstr "Tindakan" msgid "" "The following action requires to close all tabs.\n" "Do you want to continue?" msgstr "" "Tindakan berikut ini perlu untuk menutup semua tab.\n" "Apakah Anda ingin melanjutkan?" msgid "Application Shortcuts" msgstr "" msgid "Global" msgstr "" msgid "Preferences" msgstr "" msgid "Search menu" msgstr "" msgid "Toggle menu" msgstr "" msgid "Previous tab" msgstr "" msgid "Next tab" msgstr "" msgid "Shortcuts" msgstr "" msgid "Quit" msgstr "Keluar" msgid "Edition Shortcuts" msgstr "" msgid "Text Entries" msgstr "" msgid "Cut selected text" msgstr "" msgid "Copy selected text" msgstr "" msgid "Paste copied text" msgstr "" msgid "Next entry" msgstr "" msgid "Previous entry" msgstr "" msgid "Relation Entries" msgstr "" msgid "Create new relation" msgstr "" msgid "Open/Search relation" msgstr "" msgid "List Entries" msgstr "" msgid "Switch view" msgstr "" msgid "Create/Select new line" msgstr "" msgid "Open relation" msgstr "" msgid "Mark line for deletion/removal" msgstr "" msgid "Mark line for removal" msgstr "" msgid "Unmark line for deletion" msgstr "" msgid "List/Tree Shortcuts" msgstr "" msgid "Move Cursor" msgstr "Pindahkan Kursor" msgid "Move right" msgstr "Pindahkan ke kanan" msgid "Move left" msgstr "Pindahkan ke kiri" msgid "Move up" msgstr "Naikkan" msgid "Move down" msgstr "Turunkan" msgid "Move up of one page" msgstr "Naik satu halaman" msgid "Move down of one page" msgstr "Turun satu halaman" msgid "Move to top" msgstr "Pindahkan ke paling atas" msgid "Move to bottom" msgstr "Pindahkan ke paling bawah" msgid "Move to parent" msgstr "Pindahkan sebagai induk" #, fuzzy msgid "Edition" msgstr "Sunting" msgid "Copy selected rows" msgstr "" msgid "Insert copied rows" msgstr "" msgid "Select all" msgstr "Pilih semua" msgid "Unselect all" msgstr "" msgid "Select parent" msgstr "" msgid "Select/Activate current row" msgstr "" msgid "Toggle selection" msgstr "" msgid "Expand/Collapse" msgstr "" msgid "Expand row" msgstr "" msgid "Collapse row" msgstr "" msgid "Toggle row" msgstr "" msgid "Collapse all rows" msgstr "" msgid "Expand all rows" msgstr "" msgid "Close Tab" msgstr "" msgid "modularity, scalability and security" msgstr "" msgid "translator-credits" msgstr "Ferry" #, python-format msgid "Attachments (%s)" msgstr "Lampiran-Lampiran (%s)" msgid "Code Scanner" msgstr "" msgid "Code" msgstr "" msgid "Profile Editor" msgstr "" msgid "Profile" msgstr "" msgid "Add new profile" msgstr "" msgid "Remove selected profile" msgstr "" msgid "Host:" msgstr "" msgid "Database:" msgstr "" msgid "Fetching databases list" msgstr "" msgid "Username:" msgstr "" msgid "Incompatible version of the server." msgstr "Versi server tidak sesuai." msgid "Could not connect to the server." msgstr "Tidak dapat terhubung ke server." msgid "Login" msgstr "" msgid "_Cancel" msgstr "" msgid "Cancel connection to the Tryton server" msgstr "" msgid "C_onnect" msgstr "" msgid "Connect the Tryton server" msgstr "" msgid "Profile:" msgstr "" msgid "Host / Database information" msgstr "" msgid "User name:" msgstr "" msgid "Unable to complete email entry" msgstr "" #, python-format msgid "E-mail %s" msgstr "E-mail %s" msgid "To:" msgstr "Kepada:" msgid "Cc:" msgstr "" msgid "Bcc:" msgstr "" msgid "Subject:" msgstr "" msgid "Body" msgstr "" msgid "Reports" msgstr "Laporan-Laporan" msgid "Attachments" msgstr "Lampiran-Lampiran" msgid "Files" msgstr "Berkas-Berkas" msgid "Send" msgstr "Kirim" msgid "Select File" msgstr "Pilih Berkas" msgid "Remove File" msgstr "Hapus Berkas" msgid "Select" msgstr "Pilih" msgid "Add..." msgstr "" msgid "Preview" msgstr "Pratinjau" msgid "Previous" msgstr "Sebelumnya" msgid "Next" msgstr "Berikutnya" #, python-format msgid "Attachment (%s)" msgstr "Lampiran (%s)" #, python-format msgid "Note (%d/%d)" msgstr "Catatan (%d/%d)" msgid "You have to select one record." msgstr "" msgid "Are you sure to remove this record?" msgstr "" msgid "Are you sure to remove those records?" msgstr "" msgid "Records not removed." msgstr "" msgid "Records removed." msgstr "" msgid "Working now on the duplicated record(s)." msgstr "" msgid "Record saved." msgstr "" msgid "" "This record has been modified\n" "do you want to save it?" msgstr "" msgid "Launch action" msgstr "" msgid "Relate" msgstr "" msgid "Open related records" msgstr "" msgid "Report" msgstr "Laporan" msgid "Open report" msgstr "Buka Laporan" msgid "Print" msgstr "Cetak" msgid "Print report" msgstr "Cetak laporan" msgid "_Copy URL" msgstr "_Salin URL" msgid "Copy URL into clipboard" msgstr "" msgid "Unknown" msgstr "" msgid "Limit" msgstr "Batasan" msgid "Search Limit Settings" msgstr "" msgid "Limit:" msgstr "" #, python-format msgid "Logs (%s)" msgstr "Log (%s)" msgid "Model:" msgstr "Model:" msgid "ID:" msgstr "ID:" msgid "Created by:" msgstr "Dibuat oleh:" msgid "Created at:" msgstr "Dibuat pada:" msgid "Last Modified by:" msgstr "" msgid "Last Modified at:" msgstr "" #, python-format msgid "Notes (%s)" msgstr "" msgid "Edit User Preferences" msgstr "" msgid "Preference" msgstr "" msgid "Revision" msgstr "Revisi" msgid "Select a revision" msgstr "" msgid "Revision:" msgstr "Revisi:" msgid "_Switch View" msgstr "" msgid "Switch View" msgstr "" msgid "_Previous" msgstr "" msgid "Previous Record" msgstr "" msgid "_Next" msgstr "" msgid "Next Record" msgstr "" msgid "_Search" msgstr "" msgid "_New" msgstr "" msgid "Create a new record" msgstr "" msgid "_Save" msgstr "" msgid "Save this record" msgstr "" msgid "_Reload/Undo" msgstr "" msgid "Reload/Undo" msgstr "" msgid "_Duplicate" msgstr "" msgid "_Delete..." msgstr "" msgid "View _Logs..." msgstr "" msgid "Show revisions..." msgstr "" msgid "A_ttachments..." msgstr "" msgid "Add an attachment to the record" msgstr "" msgid "_Notes..." msgstr "" msgid "Add a note to the record" msgstr "" msgid "_Actions..." msgstr "" msgid "_Relate..." msgstr "" msgid "_Report..." msgstr "" msgid "_Print..." msgstr "" msgid "_E-Mail..." msgstr "" msgid "Send an e-mail using the record" msgstr "" msgid "_Export Data..." msgstr "" msgid "_Import Data..." msgstr "" msgid "Copy _URL..." msgstr "" msgid "_Close Tab" msgstr "" msgid "All fields" msgstr "" msgid "_Add" msgstr "" msgid "_Remove" msgstr "" msgid "_Clear" msgstr "" msgid "Fields selected" msgstr "" msgid "CSV Parameters" msgstr "" msgid "Delimiter:" msgstr "" msgid "Quote char:" msgstr "" msgid "Encoding:" msgstr "" msgid "Use locale format" msgstr "" msgid "Field name" msgstr "" #, python-format msgid "CSV Export: %s" msgstr "" msgid "_Save Export" msgstr "" msgid "_URL Export" msgstr "" msgid "_Delete Export" msgstr "" msgid "Predefined exports" msgstr "" msgid "Name" msgstr "Nama" msgid "Open" msgstr "Buka" msgid "Save" msgstr "Simpan" msgid "Listed Records" msgstr "" #, fuzzy msgid "Selected Records" msgstr "Pilih satu warna" msgid "Ignore search limit" msgstr "" msgid "Add field names" msgstr "" #, python-format msgid "%s (string)" msgstr "%s (string)" #, python-format msgid "%s (model name)" msgstr "%s (nama model)" #, python-format msgid "%s/Record Name" msgstr "" msgid "What is the name of this export?" msgstr "" #, python-format msgid "Override '%s' definition?" msgstr "" #, python-format msgid "%d record saved." msgstr "" #, python-format msgid "%d records saved." msgstr "" msgid "Export failed" msgstr "" msgid "Link" msgstr "Tautan" msgid "Delete" msgstr "Hapus" msgid "Discard changes" msgstr "" msgid "Save and New" msgstr "" msgid "Add and New" msgstr "" msgid "Add" msgstr "Tambah" msgid "Apply changes" msgstr "" msgid "Switch" msgstr "" msgid "Remove " msgstr "Hapus " msgid "Create a new record " msgstr "" msgid "Delete selected record " msgstr "" msgid "Undelete selected record " msgstr "" #, python-format msgid "CSV Import: %s" msgstr "" msgid "_Auto-Detect" msgstr "" msgid "File to Import:" msgstr "" msgid "Open..." msgstr "Buka..." msgid "Lines to Skip:" msgstr "" msgid "You must select an import file first." msgstr "" msgid "Detection failed" msgstr "" #, python-format msgid "Unknown column header \"%s\"" msgstr "" msgid "Error" msgstr "" msgid "Import failed" msgstr "" #, python-format msgid "%d record imported." msgstr "" #, python-format msgid "%d records imported." msgstr "" msgid "Search" msgstr "" msgid "New" msgstr "Baru" #, python-format msgid "Search %s" msgstr "" msgid "Wizard" msgstr "Wisaya" msgid "ID" msgstr "ID" msgid "Created by" msgstr "Dibuat oleh" msgid "Created at" msgstr "Dibuat pada" msgid "Edited by" msgstr "Disunting oleh" msgid "Edited at" msgstr "Disunting pada" msgid "Unable to set view tree state" msgstr "" #, python-format msgid "\"%s\" is not valid according to its domain." msgstr "" #, python-format msgid "\"%s\" is required." msgstr "\"%s\" diperlukan." #, python-format msgid "The values of \"%s\" are not valid." msgstr "Nilai \"%s\" tidak valid." msgid "Pre-validation" msgstr "" msgid ":" msgstr ":" msgid "Scan" msgstr "" msgid "Image Size" msgstr "Ukuran Gambar" msgid "Width:" msgstr "Lebar:" msgid "Height:" msgstr "Tinggi:" msgid "PNG image (*.png)" msgstr "" msgid "Save As" msgstr "Simpan Sebagai" msgid "Image size too large." msgstr "Ukuran gambar terlalu besar." msgid "Copy" msgstr "Salin" msgid "Paste" msgstr "" msgid ".." msgstr ".." msgid "Open filters" msgstr "" msgid "Show bookmarks of filters" msgstr "" msgid "Remove this bookmark" msgstr "" msgid "Bookmark this filter" msgstr "" msgid "Show active records" msgstr "" msgid "Show inactive records" msgstr "" msgid "Bookmark Name:" msgstr "" msgid "Find" msgstr "" msgid "Today" msgstr "Hari ini" msgid "go back" msgstr "kembali" msgid "go forward" msgstr "maju" msgid "previous year" msgstr "tahun sebelumnya" msgid "next year" msgstr "tahun berikutnya" msgid "Day" msgstr "" msgid "Week" msgstr "Pekan" msgid "Month" msgstr "" msgid "Select..." msgstr "Pilih..." msgid "Clear" msgstr "Bersihkan" msgid "All files" msgstr "" msgid "Show plain text" msgstr "" msgid "Add value" msgstr "" #, python-format msgid "Remove \"%s\"" msgstr "" msgid "Images" msgstr "" msgid "Add existing record" msgstr "" msgid "Remove selected record" msgstr "" msgid "Open the record " msgstr "" msgid "Clear the field " msgstr "" msgid "Search a record " msgstr "" msgid "Edit selected record" msgstr "" msgid "Delete selected record" msgstr "" #, python-format msgid "%s%%" msgstr "%s%%" msgid "Choose a language" msgstr "Pilih bahasa" msgid "Translation" msgstr "Terjemahan" msgid "Edit" msgstr "Sunting" msgid "Fuzzy" msgstr "" msgid "You need to save the record before adding translations." msgstr "" msgid "No other language available." msgstr "Tidak ada bahasa lain yang tersedia." msgid "#ERROR" msgstr "" msgid "Translate view" msgstr "" ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1745680285.1241102 tryton-7.0.24/tryton/data/locale/it/0000755000175000017500000000000015003173635015401 5ustar00cedced././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1745680285.1641107 tryton-7.0.24/tryton/data/locale/it/LC_MESSAGES/0000755000175000017500000000000015003173635017166 5ustar00cedced././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1745680279.0 tryton-7.0.24/tryton/data/locale/it/LC_MESSAGES/tryton.mo0000644000175000017500000003523515003173627021073 0ustar00cedced,| )1 AMPTVh ;O_ oy #% (4ETYh}&      -: BNd~    ! - 8D V `lt{   &06 FQa gq  #= Sagn }     1 ;FU dpx    "' < JVhp    #09APY ^l     ", 5?D LWiy   ( ;Ea r |    Id5z8   # ? G V [ |      ! !&! ,!:! ?!`!(g! !!!!%!7! " 0"<"A"I" P" ["e" t" " """"" " " " "" "" ## # &#2#4#E# G#h#$j###<## $$$&$9$;$=$%%% &&%& 7&D&G&K&M&b&{&&&"& &&& ''-'L'i''' ' ''''&('<( d(r( {( (((((( ( (('),)C)G)Y)o)w)~)) ) ) )))* ***"E*h*z** * * * ** * * ++#+ ;+I+d+~+ ++++"+ + +++ ,,(, .,8, V,d,w,},,,,,,,,,,,,"-'-B-T-[-c- u---- -- -3-(.+.4. C.Q.q.. .. . ......//1/4/#M/q// / ///// //0 00'0 E0S0 l0 w0 0 00000 0 000 111'1;1 K1 U1a1s1 111111 2 2 2#2 )2 42B2Z2t222"2222 3323B3R3 e3r33 33333 4$4-4 44 A4N4 T4 b4Mp4434=5F5I5N5d5z5'55 5595*#6*N6)y6 6666 6 777 (7'27Z7(a77777,7B7,8 >8I8Q8Z8c8 r8}8 8 8 8 8 88 8 8 8 899 #90979K9 R9`9r9t9999%99&9L9F:#d:::::%d record imported.%d record saved.%d records imported.%d records saved.%s (%s)%s (model name)%s (string), ...:All fieldsFields selectedPredefined exportsCreate...Search...A new version is available!A_ttachments...About...ActionActions...Add a note to the recordAdd an attachment to the recordAdd existing recordAdd field namesAdd new profileAdd valueAdd...All filesAlways ignore this warning.Application ErrorApplication ShortcutsAre you sure to remove this record?Are you sure to remove those records?Attachment (%s)AttachmentsAttachments (%s)Attachments...BodyBookmark Name:Bookmark this filterBy: CSV Export: %sCSV ParametersC_onnectCancelCancel connection to the Tryton serverCancel savingCc:Check URL: %sCheck VersionClearCloseClose TabCollapse all rowsCollapse rowCompareCompare: %sConcurrency ExceptionConnect the Tryton serverCopy URL into clipboardCopy _URL...Copy selected textCould not connect to the server.Create a new recordCreate new relationCreate/Select new lineCreated atCreated at:Created byCreated by:Cut selected textDatabase:Date FormatDefaultDeleteDelete selected record Delimiter:Display formatDisplayed date formatDisplayed valueDo you want to proceed?DownloadE-Mail...EditEdit User PreferencesEdit...Encoding:ErrorExpand all rowsExpand rowExpand/CollapseFalseFavoritesFetching databases listField nameFile to Import:FindFormFormatFuzzyGlobalHelpHost / Database informationHost:ID:IconsIgnore search limitImage SizeImagesIncompatible version of the server.Keyboard Shortcuts...Launch actionLimitLimit:Lines to Skip:List EntriesList/Tree ShortcutsListed RecordsLoginLogs (%s)MManage...Mark line for deletion/removalMark line for removalModel:Move CursorMove downMove down of one pageMove leftMove rightMove to bottomMove to parentMove to topMove upMove up of one pageNameNewNextNext RecordNext entryNext tabNoNo action defined.No other language available.No result found.Note (%d/%d)Notes (%s)Notes...OKOpenOpen related recordsOpen relationOpen reportOpen the calendarOpen...Open/Search relationOptionsOverride '%s' definition?PDA ModePaste copied textPreferencePreferencesPreferences...PreviousPrevious RecordPrevious entryPrevious tabPrintPrint reportPrint...ProfileProfile EditorProfile:QuitRecord saved.Records not removed.Records removed.RelateRelate...Relation EntriesReload/UndoRemove "%s"Remove selected profileRemove selected recordRemove this bookmarkReportReport BugReport...RevisionRevision:SaveSave AsSave As...Save Column WidthSave Tree StateSave this recordSave your current versionSearchSearch Limit SettingsSearch Limit...Search menuSee the modified versionSelectSelect a revisionSelect allSelect parentSelect your actionSelect...Select/Activate current rowSelected RecordsSelectionShortcutsShow bookmarks of filtersShow plain textShow revisions...Spell CheckingSubject:SwitchSwitch ViewSwitch viewTextText EntriesText and IconsThe following action requires to close all tabs. Do you want to continue?The number of decimalThis record has been modified do you want to save it?This record has been modified while you were editing it.To:TodayToggle menuToggle rowToggle selectionToo many requests. Try again later.ToolbarTranslate viewTrueUnable to check for new version.Unable to set locale %sUnable to set view tree stateUndelete selected record UnknownUnmark line for deletionUnselect allUse locale formatUser name:Username:ValueView _Logs...WeekWhat is the name of this export?WizardWorking now on the duplicated record(s).Write AnywayYYesYou have to select one record.You must select an import file first.You need to save the record before adding translations.Your selection:_Actions..._Add_Cancel_Clear_Close Tab_Copy URL_Delete Export_Delete..._Duplicate_E-Mail..._Export Data..._Import Data..._New_Next_Notes..._Previous_Print..._Reload/Undo_Remove_Report..._Save_Save Export_Search_Switch View_URL Exportddevelopment modehlogging everything at INFO levelmmodularity, scalability and securitysspecify alternate config filespecify the log level: DEBUG, INFO, WARNING, ERROR, CRITICALspecify the login userspecify the server hostname:portttranslator-creditswyProject-Id-Version: PROJECT VERSION Report-Msgid-Bugs-To: EMAIL@ADDRESS POT-Creation-Date: 2025-04-26 17:11+0200 PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE Last-Translator: FULL NAME Language: it Language-Team: it Plural-Forms: nplurals=2; plural=(n != 1); MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: 8bit Generated-By: Babel 2.17.0 %d record importati.%d record salvato.%d record importati.%d record salvato.%s (%s)%s (nome modello)%s (stringa), ...:Tutti i campiCampi selezionatiesportazioni predefiniteCrea...Cerca...Una nuova versione è disponibile!A_llegati...Informazioni su...AzioneAzioni...Aggiungi un appunto sul recordAggiungi un allegato al recordAggiungi un record esistenteAggiungi nomi dei campoAggiungi nuovo profiloAggiungi valoreInserisci...Tutti i filesIgnorare sempre l'avvisoErrore dell'applicazioneScorciatoie per le applicazioniSei sicuro di rimuovere questo record?Sei sicuro di rimuovere questi records?Allegato (%s)AllegatiAllegati (%s)Allegati...CorpoNome Segnalibro:contrassegnare questo filtroper: Esportazione CSV: %sParametri CSVC_onnettiAnnullaAnnulla la connessione al server TrytonAnnulla il salvataggioCc:Controlla URL: %sControlla la versionePulisciChiudiChiudere la schedaComprimi tutte le righeComprimi rigaConfrontaConfronta: %sEccezione di concorrenzaConnetti il server TrytonCopia l'URL negli appuntiCopia _URL...Copia il testo selezionatoImpossibile connettersi al server.Crea nuovo recordCrea nuova relazioneCrea/Seleziona nuova rigaCreato ilCreato il:Creato daCreato da:Taglia testo selezionatoDatabase:Formato dataPredefinitoEliminaElimina il record selezionato Delimitatore:Formato di visualizzazioneFormato data visualizzatoValore visualizzatoProcedere?ScaricaE-MailModificaModifica le preferenze dell'utenteModifica...Codifica:ErroreEspandi tutte le righeEspandi rigaEspandi/RiduciFalsePreferitiPreleva la lista dei databaseNome di campoFile da importare:TrovaModuloFormatoVagoGlobaleAiutoInformazione su Host/ databaseHost:ID:IconeIgnora limite di ricercaDimensione immagineImmaginiVersione del server incompatibile.Scorciatoie da tastiera...Lanciare l'azioneLimiteLimite:Righe da saltare:Elenca vociScorciatoie Elenco/AlberoRecord elencatiAccediRegistri (%s)MGestisci...Contrassegna la riga per la cancellazione/rimozioneContrassegnare la linea per la rimozioneModello:Sposta cursoreSpostati giùSpostati in basso di una paginaSpostati a sinistraSpostati a destraVai alla fineMuoviti sul padreVai in cimaSpostati suSpostati alto di una paginaNomeNuovoProssimoProssimo RecordVoce successivaScheda successivaNoNessuna azione definita.Nessun altra lingua è disponibile.Nessun risultato trovato.Appunto (%d/%d)Appunti (%s)Appunti...OKApriApri record correlatiApri relazioneApri rapportoApertura calendarioApri...Apri/Cerca relazioneOpzioniSostituisci definizione '%s'?Modalità PDAIncolla il testo copiatoPreferenzaPreferenzePreferenze...PrecedenteRecord PrecedenteVoce precedenteScheda precedenteStampaStampa reportStampa...ProfiloEditor del profiloProfilo:EsciRecord salvato.Record non rimossi.Record rimossi.CollegatoRiferire...Voci di relazioneRicarica/RipristinaRimuovi "%s"Rimuovi i profili selezionatiRimuovi i record selezionatiRimuovi questo segnalibroRapportoSegnala l'erroreSegnala...RevisioneRevisione:SalvaSalva comeSalva come...Salva larghezza colonnaSalva diagramma ad alberoSalva questo record.Salva la tua versione attualeRicercaImpostazione dei limiti di ricercaLimite di ricerca...Menu di ricercaVedi la versione modificataSelezionaScegli una revisioneSeleziona tuttoSeleziona padreSeleziona l'azioneSeleziona...Seleziona/Attiva riga correnteRecord selezionatiSelezioneScorciatoie da tastieraMostra segnalibri dei filtriMostra testo sempliceMostra revisioni...Controllo ortogbraficoOggetto:CambiaCambia vistaCambia vistaTestoVoci di testoTesto e iconeL'azione seguente richiede la chiusura di tutte le finestre. Vuoi continuare?Il numero di decimaliQuesto record è stato modificato intendi salvarlo?Questo record è stato modificato mentre lo stavi compilando.a:OggiAttiva/disattiva menuAttiva/disattiva rigaAttiva/disattiva la selezioneTroppe richieste. Riprovare più tardi.Barra degli strumentiTraduci vistaVeroImpossibile verificare la presenza di una nuova versione.Impossibile impostare la localizzazione %sImpossibile configurare la vista ad alberoNon eliminare il record selezionato SconosciutoDeseleziona riga da eliminareDeseleziona tuttoUsa il formato localeNome utente:Nome utente:ValoreVisualizza_Log...SettimanaQual è il nome di questa esportazione?WizardStai ora lavorando nel record duplicato.Scrivi comunqueASìDevi selezionare un record.Devi prima selezionare un file da importare.È necessario salvare il record prima di aggiungere le traduzioni.La tua selezione:_Azioni...Profilo_Annulla_Pulisci_Chiudi scheda_Copia URL_Elimina Esportazione_Eliimina_Duplicato_E-Mail..._Esporta dati_Importa dati_Nuovo_Successivo_Appunti..._Precedente_Stampa..._Ricarica/Ripristina_Rimuovi_Rapporto..._Salva_Salva esportazione_Cerca_Cambia vista_URL Esportazionegmodo sviluppatorehregistrare tutto a livello INFOmmodularità, scalabilità e sicurezzasindica un altro file di configurazionespecifica il livello di registrazione: DEBUG, INFO, WARNING, ERROR, CRITICALspecifica l'utente di accessospecifica hostname:porta del serveroLuca CristaldiSa././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1703673879.0 tryton-7.0.24/tryton/data/locale/it/LC_MESSAGES/tryton.po0000644000175000017500000004634614543000027021072 0ustar00cedced# Translations template for tryton. # Copyright (C) 2016 Tryton # This file is distributed under the same license as the tryton project. # FIRST AUTHOR , 2016. msgid "" msgstr "Content-Type: text/plain; charset=utf-8\n" msgid "specify alternate config file" msgstr "indica un altro file di configurazione" msgid "development mode" msgstr "modo sviluppatore" msgid "logging everything at INFO level" msgstr "registrare tutto a livello INFO" msgid "specify the log level: DEBUG, INFO, WARNING, ERROR, CRITICAL" msgstr "" "specifica il livello di registrazione: DEBUG, INFO, WARNING, ERROR, CRITICAL" msgid "specify the file used to output logging information" msgstr "" msgid "specify the login user" msgstr "specifica l'utente di accesso" msgid "specify the server hostname:port" msgstr "specifica hostname:porta del server" msgid "disable thread usage" msgstr "" #, python-format msgid "Unable to set locale %s" msgstr "Impossibile impostare la localizzazione %s" msgid ", " msgstr ", " msgid ",..." msgstr "" #, python-format msgid "%s (%s)" msgstr "%s (%s)" msgid "Select your action" msgstr "Seleziona l'azione" msgid "No action defined." msgstr "Nessuna azione definita." msgid "By: " msgstr "per: " msgid "Selection" msgstr "Selezione" msgid "Cancel" msgstr "Annulla" msgid "OK" msgstr "OK" msgid "Your selection:" msgstr "La tua selezione:" msgid "Save As..." msgstr "Salva come..." msgid "Do you want to proceed?" msgstr "Procedere?" msgid "Always ignore this warning." msgstr "Ignorare sempre l'avviso" msgid "No" msgstr "No" msgid "Yes" msgstr "Sì" msgid "Concurrency Exception" msgstr "Eccezione di concorrenza" msgid "This record has been modified while you were editing it." msgstr "Questo record è stato modificato mentre lo stavi compilando." msgid "Cancel saving" msgstr "Annulla il salvataggio" msgid "Compare" msgstr "Confronta" msgid "See the modified version" msgstr "Vedi la versione modificata" msgid "Write Anyway" msgstr "Scrivi comunque" msgid "Save your current version" msgstr "Salva la tua versione attuale" #, python-format msgid "Compare: %s" msgstr "Confronta: %s" msgid "Close" msgstr "Chiudi" msgid "Application Error" msgstr "Errore dell'applicazione" msgid "Report Bug" msgstr "Segnala l'errore" #, python-format msgid "Check URL: %s" msgstr "Controlla URL: %s" msgid "Unable to check for new version." msgstr "Impossibile verificare la presenza di una nuova versione." msgid "A new version is available!" msgstr "Una nuova versione è disponibile!" msgid "Download" msgstr "Scarica" #, fuzzy msgid "Could not get a session." msgstr "Impossibile connettersi al server." msgid "Too many requests. Try again later." msgstr "Troppe richieste. Riprovare più tardi." #, fuzzy msgid "Not found." msgstr "Nessun risultato trovato." #, fuzzy msgid "Not Found." msgstr "Nessun risultato trovato." msgid "..." msgstr "..." msgid "Search..." msgstr "Cerca..." msgid "Create..." msgstr "Crea..." #, fuzzy, python-format msgid "Create \"%s\"..." msgstr "Crea \"%s\"..." msgid "Value" msgstr "Valore" msgid "Displayed value" msgstr "Valore visualizzato" msgid "Format" msgstr "Formato" msgid "Display format" msgstr "Formato di visualizzazione" msgid "Open the calendar" msgstr "Apertura calendario" msgid "Date Format" msgstr "Formato data" msgid "Displayed date format" msgstr "Formato data visualizzato" msgid "y" msgstr "a" msgid "True" msgstr "Vero" msgid "t" msgstr "o" msgid "False" msgstr "False" msgid "Digits" msgstr "" msgid "The number of decimal" msgstr "Il numero di decimali" #, fuzzy msgid "Template" msgstr "Collegato" msgid "Edit..." msgstr "Modifica..." #, fuzzy msgid "View Logs..." msgstr "Visualizza_Log..." msgid "Attachments..." msgstr "Allegati..." msgid "Notes..." msgstr "Appunti..." msgid "Actions..." msgstr "Azioni..." msgid "Relate..." msgstr "Riferire..." msgid "Report..." msgstr "Segnala..." msgid "Print..." msgstr "Stampa..." msgid "E-Mail..." msgstr "E-Mail" msgid "Bold" msgstr "" msgid "Italic" msgstr "" msgid "Underline" msgstr "" msgid "Align Left" msgstr "" msgid "Align Center" msgstr "" msgid "Align Right" msgstr "" msgid "Justify" msgstr "" msgid "Foreground Color" msgstr "" msgid "Select a color" msgstr "" msgid "Y" msgstr "A" msgid "M" msgstr "M" msgid "w" msgstr "S" msgid "d" msgstr "g" msgid "h" msgstr "h" msgid "m" msgstr "m" msgid "s" msgstr "s" msgid "Preferences..." msgstr "Preferenze..." msgid "Toolbar" msgstr "Barra degli strumenti" msgid "Default" msgstr "Predefinito" msgid "Text and Icons" msgstr "Testo e icone" msgid "Text" msgstr "Testo" msgid "Icons" msgstr "Icone" msgid "Form" msgstr "Modulo" msgid "Save Column Width" msgstr "Salva larghezza colonna" msgid "Save Tree State" msgstr "Salva diagramma ad albero" msgid "Spell Checking" msgstr "Controllo ortogbrafico" msgid "Play Sound for Code Scanner" msgstr "" msgid "PDA Mode" msgstr "Modalità PDA" msgid "Search Limit..." msgstr "Limite di ricerca..." msgid "Check Version" msgstr "Controlla la versione" msgid "Options" msgstr "Opzioni" #, fuzzy msgid "Documentation..." msgstr "Azioni..." msgid "Keyboard Shortcuts..." msgstr "Scorciatoie da tastiera..." msgid "About..." msgstr "Informazioni su..." msgid "Help" msgstr "Aiuto" msgid "No result found." msgstr "Nessun risultato trovato." msgid "Favorites" msgstr "Preferiti" msgid "Manage..." msgstr "Gestisci..." msgid "Action" msgstr "Azione" msgid "" "The following action requires to close all tabs.\n" "Do you want to continue?" msgstr "" "L'azione seguente richiede la chiusura di tutte le finestre.\n" "Vuoi continuare?" msgid "Application Shortcuts" msgstr "Scorciatoie per le applicazioni" msgid "Global" msgstr "Globale" msgid "Preferences" msgstr "Preferenze" msgid "Search menu" msgstr "Menu di ricerca" msgid "Toggle menu" msgstr "Attiva/disattiva menu" msgid "Previous tab" msgstr "Scheda precedente" msgid "Next tab" msgstr "Scheda successiva" msgid "Shortcuts" msgstr "Scorciatoie da tastiera" msgid "Quit" msgstr "Esci" msgid "Edition Shortcuts" msgstr "" msgid "Text Entries" msgstr "Voci di testo" msgid "Cut selected text" msgstr "Taglia testo selezionato" msgid "Copy selected text" msgstr "Copia il testo selezionato" msgid "Paste copied text" msgstr "Incolla il testo copiato" msgid "Next entry" msgstr "Voce successiva" msgid "Previous entry" msgstr "Voce precedente" msgid "Relation Entries" msgstr "Voci di relazione" msgid "Create new relation" msgstr "Crea nuova relazione" msgid "Open/Search relation" msgstr "Apri/Cerca relazione" msgid "List Entries" msgstr "Elenca voci" msgid "Switch view" msgstr "Cambia vista" msgid "Create/Select new line" msgstr "Crea/Seleziona nuova riga" msgid "Open relation" msgstr "Apri relazione" msgid "Mark line for deletion/removal" msgstr "Contrassegna la riga per la cancellazione/rimozione" msgid "Mark line for removal" msgstr "Contrassegnare la linea per la rimozione" msgid "Unmark line for deletion" msgstr "Deseleziona riga da eliminare" msgid "List/Tree Shortcuts" msgstr "Scorciatoie Elenco/Albero" msgid "Move Cursor" msgstr "Sposta cursore" msgid "Move right" msgstr "Spostati a destra" msgid "Move left" msgstr "Spostati a sinistra" msgid "Move up" msgstr "Spostati su" msgid "Move down" msgstr "Spostati giù" msgid "Move up of one page" msgstr "Spostati alto di una pagina" msgid "Move down of one page" msgstr "Spostati in basso di una pagina" msgid "Move to top" msgstr "Vai in cima" msgid "Move to bottom" msgstr "Vai alla fine" msgid "Move to parent" msgstr "Muoviti sul padre" #, fuzzy msgid "Edition" msgstr "Modifica" #, fuzzy msgid "Copy selected rows" msgstr "Copia il testo selezionato" msgid "Insert copied rows" msgstr "" msgid "Select all" msgstr "Seleziona tutto" msgid "Unselect all" msgstr "Deseleziona tutto" msgid "Select parent" msgstr "Seleziona padre" msgid "Select/Activate current row" msgstr "Seleziona/Attiva riga corrente" msgid "Toggle selection" msgstr "Attiva/disattiva la selezione" msgid "Expand/Collapse" msgstr "Espandi/Riduci" msgid "Expand row" msgstr "Espandi riga" msgid "Collapse row" msgstr "Comprimi riga" msgid "Toggle row" msgstr "Attiva/disattiva riga" msgid "Collapse all rows" msgstr "Comprimi tutte le righe" msgid "Expand all rows" msgstr "Espandi tutte le righe" msgid "Close Tab" msgstr "Chiudere la scheda" msgid "modularity, scalability and security" msgstr "modularità, scalabilità e sicurezza" msgid "translator-credits" msgstr "Luca Cristaldi" #, python-format msgid "Attachments (%s)" msgstr "Allegati (%s)" msgid "Code Scanner" msgstr "" msgid "Code" msgstr "" msgid "Profile Editor" msgstr "Editor del profilo" msgid "Profile" msgstr "Profilo" msgid "Add new profile" msgstr "Aggiungi nuovo profilo" msgid "Remove selected profile" msgstr "Rimuovi i profili selezionati" msgid "Host:" msgstr "Host:" msgid "Database:" msgstr "Database:" msgid "Fetching databases list" msgstr "Preleva la lista dei database" msgid "Username:" msgstr "Nome utente:" msgid "Incompatible version of the server." msgstr "Versione del server incompatibile." msgid "Could not connect to the server." msgstr "Impossibile connettersi al server." msgid "Login" msgstr "Accedi" msgid "_Cancel" msgstr "_Annulla" msgid "Cancel connection to the Tryton server" msgstr "Annulla la connessione al server Tryton" msgid "C_onnect" msgstr "C_onnetti" msgid "Connect the Tryton server" msgstr "Connetti il server Tryton" msgid "Profile:" msgstr "Profilo:" msgid "Host / Database information" msgstr "Informazione su Host/ database" msgid "User name:" msgstr "Nome utente:" #, fuzzy msgid "Unable to complete email entry" msgstr "Impossibile impostare la localizzazione %s" #, fuzzy, python-format msgid "E-mail %s" msgstr "Email %s" msgid "To:" msgstr "a:" msgid "Cc:" msgstr "Cc:" msgid "Bcc:" msgstr "" msgid "Subject:" msgstr "Oggetto:" msgid "Body" msgstr "Corpo" #, fuzzy msgid "Reports" msgstr "Rapporto" msgid "Attachments" msgstr "Allegati" msgid "Files" msgstr "" msgid "Send" msgstr "" #, fuzzy msgid "Select File" msgstr "Seleziona tutto" #, fuzzy msgid "Remove File" msgstr "_Rimuovi" msgid "Select" msgstr "Seleziona" msgid "Add..." msgstr "Inserisci..." #, fuzzy msgid "Preview" msgstr "Precedente" msgid "Previous" msgstr "Precedente" msgid "Next" msgstr "Prossimo" #, python-format msgid "Attachment (%s)" msgstr "Allegato (%s)" #, python-format msgid "Note (%d/%d)" msgstr "Appunto (%d/%d)" msgid "You have to select one record." msgstr "Devi selezionare un record." msgid "Are you sure to remove this record?" msgstr "Sei sicuro di rimuovere questo record?" msgid "Are you sure to remove those records?" msgstr "Sei sicuro di rimuovere questi records?" msgid "Records not removed." msgstr "Record non rimossi." msgid "Records removed." msgstr "Record rimossi." msgid "Working now on the duplicated record(s)." msgstr "Stai ora lavorando nel record duplicato." msgid "Record saved." msgstr "Record salvato." msgid "" "This record has been modified\n" "do you want to save it?" msgstr "" "Questo record è stato modificato\n" "intendi salvarlo?" msgid "Launch action" msgstr "Lanciare l'azione" msgid "Relate" msgstr "Collegato" msgid "Open related records" msgstr "Apri record correlati" msgid "Report" msgstr "Rapporto" msgid "Open report" msgstr "Apri rapporto" msgid "Print" msgstr "Stampa" msgid "Print report" msgstr "Stampa report" msgid "_Copy URL" msgstr "_Copia URL" msgid "Copy URL into clipboard" msgstr "Copia l'URL negli appunti" msgid "Unknown" msgstr "Sconosciuto" msgid "Limit" msgstr "Limite" msgid "Search Limit Settings" msgstr "Impostazione dei limiti di ricerca" msgid "Limit:" msgstr "Limite:" #, python-format msgid "Logs (%s)" msgstr "Registri (%s)" msgid "Model:" msgstr "Modello:" msgid "ID:" msgstr "ID:" msgid "Created by:" msgstr "Creato da:" msgid "Created at:" msgstr "Creato il:" #, fuzzy msgid "Last Modified by:" msgstr "Ultima modifica a:" #, fuzzy msgid "Last Modified at:" msgstr "Ultima data di modifica:" #, python-format msgid "Notes (%s)" msgstr "Appunti (%s)" msgid "Edit User Preferences" msgstr "Modifica le preferenze dell'utente" msgid "Preference" msgstr "Preferenza" msgid "Revision" msgstr "Revisione" msgid "Select a revision" msgstr "Scegli una revisione" msgid "Revision:" msgstr "Revisione:" msgid "_Switch View" msgstr "_Cambia vista" msgid "Switch View" msgstr "Cambia vista" msgid "_Previous" msgstr "_Precedente" msgid "Previous Record" msgstr "Record Precedente" msgid "_Next" msgstr "_Successivo" msgid "Next Record" msgstr "Prossimo Record" msgid "_Search" msgstr "_Cerca" msgid "_New" msgstr "_Nuovo" msgid "Create a new record" msgstr "Crea nuovo record" msgid "_Save" msgstr "_Salva" msgid "Save this record" msgstr "Salva questo record." msgid "_Reload/Undo" msgstr "_Ricarica/Ripristina" msgid "Reload/Undo" msgstr "Ricarica/Ripristina" msgid "_Duplicate" msgstr "_Duplicato" msgid "_Delete..." msgstr "_Eliimina" msgid "View _Logs..." msgstr "Visualizza_Log..." msgid "Show revisions..." msgstr "Mostra revisioni..." msgid "A_ttachments..." msgstr "A_llegati..." msgid "Add an attachment to the record" msgstr "Aggiungi un allegato al record" msgid "_Notes..." msgstr "_Appunti..." msgid "Add a note to the record" msgstr "Aggiungi un appunto sul record" msgid "_Actions..." msgstr "_Azioni..." #, fuzzy msgid "_Relate..." msgstr "Riferire..." msgid "_Report..." msgstr "_Rapporto..." msgid "_Print..." msgstr "_Stampa..." msgid "_E-Mail..." msgstr "_E-Mail..." #, fuzzy msgid "Send an e-mail using the record" msgstr "Aggiungi un appunto sul record" msgid "_Export Data..." msgstr "_Esporta dati" msgid "_Import Data..." msgstr "_Importa dati" msgid "Copy _URL..." msgstr "Copia _URL..." msgid "_Close Tab" msgstr "_Chiudi scheda" msgid "All fields" msgstr "Tutti i campi" msgid "_Add" msgstr "Profilo" msgid "_Remove" msgstr "_Rimuovi" msgid "_Clear" msgstr "_Pulisci" msgid "Fields selected" msgstr "Campi selezionati" msgid "CSV Parameters" msgstr "Parametri CSV" msgid "Delimiter:" msgstr "Delimitatore:" msgid "Quote char:" msgstr "" msgid "Encoding:" msgstr "Codifica:" msgid "Use locale format" msgstr "Usa il formato locale" msgid "Field name" msgstr "Nome di campo" #, python-format msgid "CSV Export: %s" msgstr "Esportazione CSV: %s" msgid "_Save Export" msgstr "_Salva esportazione" msgid "_URL Export" msgstr "_URL Esportazione" msgid "_Delete Export" msgstr "_Elimina Esportazione" msgid "Predefined exports" msgstr "esportazioni predefinite" msgid "Name" msgstr "Nome" msgid "Open" msgstr "Apri" msgid "Save" msgstr "Salva" msgid "Listed Records" msgstr "Record elencati" msgid "Selected Records" msgstr "Record selezionati" msgid "Ignore search limit" msgstr "Ignora limite di ricerca" msgid "Add field names" msgstr "Aggiungi nomi dei campo" #, python-format msgid "%s (string)" msgstr "%s (stringa)" #, python-format msgid "%s (model name)" msgstr "%s (nome modello)" #, fuzzy, python-format msgid "%s/Record Name" msgstr "%s (nome record)" msgid "What is the name of this export?" msgstr "Qual è il nome di questa esportazione?" #, python-format msgid "Override '%s' definition?" msgstr "Sostituisci definizione '%s'?" #, python-format msgid "%d record saved." msgstr "%d record salvato." #, python-format msgid "%d records saved." msgstr "%d record salvato." msgid "Export failed" msgstr "" msgid "Link" msgstr "" msgid "Delete" msgstr "Elimina" msgid "Discard changes" msgstr "" msgid "Save and New" msgstr "" #, fuzzy msgid "Add and New" msgstr "Aggiungi valore" #, fuzzy msgid "Add" msgstr "Profilo" msgid "Apply changes" msgstr "" msgid "Switch" msgstr "Cambia" msgid "Remove " msgstr "" #, fuzzy msgid "Create a new record " msgstr "Crea nuovo record" msgid "Delete selected record " msgstr "Elimina il record selezionato " msgid "Undelete selected record " msgstr "Non eliminare il record selezionato " #, python-format msgid "CSV Import: %s" msgstr "" #, fuzzy msgid "_Auto-Detect" msgstr "Autorilevamento" msgid "File to Import:" msgstr "File da importare:" msgid "Open..." msgstr "Apri..." msgid "Lines to Skip:" msgstr "Righe da saltare:" msgid "You must select an import file first." msgstr "Devi prima selezionare un file da importare." msgid "Detection failed" msgstr "" #, python-format msgid "Unknown column header \"%s\"" msgstr "" msgid "Error" msgstr "Errore" msgid "Import failed" msgstr "" #, python-format msgid "%d record imported." msgstr "%d record importati." #, python-format msgid "%d records imported." msgstr "%d record importati." msgid "Search" msgstr "Ricerca" msgid "New" msgstr "Nuovo" #, python-format msgid "Search %s" msgstr "" msgid "Wizard" msgstr "Wizard" #, fuzzy msgid "ID" msgstr "ID" msgid "Created by" msgstr "Creato da" msgid "Created at" msgstr "Creato il" #, fuzzy msgid "Edited by" msgstr "Modifica" #, fuzzy msgid "Edited at" msgstr "Modifica" msgid "Unable to set view tree state" msgstr "Impossibile configurare la vista ad albero" #, python-format msgid "\"%s\" is not valid according to its domain." msgstr "" #, python-format msgid "\"%s\" is required." msgstr "" #, python-format msgid "The values of \"%s\" are not valid." msgstr "" msgid "Pre-validation" msgstr "" msgid ":" msgstr ":" msgid "Scan" msgstr "" msgid "Image Size" msgstr "Dimensione immagine" msgid "Width:" msgstr "" msgid "Height:" msgstr "" msgid "PNG image (*.png)" msgstr "" msgid "Save As" msgstr "Salva come" msgid "Image size too large." msgstr "" msgid "Copy" msgstr "" msgid "Paste" msgstr "" msgid ".." msgstr "" msgid "Open filters" msgstr "" msgid "Show bookmarks of filters" msgstr "Mostra segnalibri dei filtri" msgid "Remove this bookmark" msgstr "Rimuovi questo segnalibro" msgid "Bookmark this filter" msgstr "contrassegnare questo filtro" msgid "Show active records" msgstr "" msgid "Show inactive records" msgstr "" msgid "Bookmark Name:" msgstr "Nome Segnalibro:" msgid "Find" msgstr "Trova" msgid "Today" msgstr "Oggi" msgid "go back" msgstr "" msgid "go forward" msgstr "" msgid "previous year" msgstr "" msgid "next year" msgstr "" msgid "Day" msgstr "" msgid "Week" msgstr "Settimana" #, fuzzy msgid "Month" msgstr "Vista mensile" msgid "Select..." msgstr "Seleziona..." msgid "Clear" msgstr "Pulisci" msgid "All files" msgstr "Tutti i files" msgid "Show plain text" msgstr "Mostra testo semplice" msgid "Add value" msgstr "Aggiungi valore" #, python-format msgid "Remove \"%s\"" msgstr "Rimuovi \"%s\"" msgid "Images" msgstr "Immagini" msgid "Add existing record" msgstr "Aggiungi un record esistente" msgid "Remove selected record" msgstr "Rimuovi i record selezionati" msgid "Open the record " msgstr "" msgid "Clear the field " msgstr "" msgid "Search a record " msgstr "" #, fuzzy msgid "Edit selected record" msgstr "Modifica il record selezionato " #, fuzzy msgid "Delete selected record" msgstr "Rimuovi i record selezionati" #, python-format msgid "%s%%" msgstr "" msgid "Choose a language" msgstr "" #, fuzzy msgid "Translation" msgstr "Traduzione" msgid "Edit" msgstr "Modifica" msgid "Fuzzy" msgstr "Vago" msgid "You need to save the record before adding translations." msgstr "È necessario salvare il record prima di aggiungere le traduzioni." msgid "No other language available." msgstr "Nessun altra lingua è disponibile." msgid "#ERROR" msgstr "" msgid "Translate view" msgstr "Traduci vista" ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1745680285.1274436 tryton-7.0.24/tryton/data/locale/ja_JP/0000755000175000017500000000000015003173635015750 5ustar00cedced././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1745680285.1641107 tryton-7.0.24/tryton/data/locale/ja_JP/LC_MESSAGES/0000755000175000017500000000000015003173635017535 5ustar00cedced././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1745680279.0 tryton-7.0.24/tryton/data/locale/ja_JP/LC_MESSAGES/tryton.mo0000644000175000017500000001562515003173627021443 0ustar00cedced<\]bdv #% < K W ] i m | &       ) = V e t           % 5 : B F Q X k  !              1 C M _ j v           * 9 B I }`      ! ' 5V] dqs         $& (IKi%9_fm$$(( ?JRZpt * !'*R$n; *-4$J oz ? "&IPX\l$s1   $1 5@ GRVi"- )B3I } '/-6d,'0X_ p~1   4 K V"a     ( I!M!o of :All fieldsPredefined exportsActionAddAdd _field namesAdd an attachment to the recordAll filesAlways ignore this warning.Are you sure to remove this record?Are you sure to remove those records?Attachment(%d)Attachment:Body:Bug TrackerCC:CSV ParametersC_onnectCancel connection to the Tryton serverClearClose TabCommand Line:CompareConcurrency ExceptionConnect the Tryton serverCopy selected textCreate a new recordCreate new relationCreated new bug with ID Creation Date:Creation User:Cut selected textDatabase:DeleteEdit User PreferencesEdition WidgetsEmailEmail Program SettingsEncoding:ErrorError opening CSV fileError: Exception:FalseField nameFile to Import:FindHeight:ID:Image SizeImagesKeyboard ShortcutsLatest Modification Date:Latest Modification by:Legend of Available Placeholders:Limit:Lines to Skip:LinkLoginMModel:NameNewNextNext RecordNext widgetOpenOpen...Open/Search relationPNG image (*.png)Password:Paste copied textPreferencePreferencesPreviousPrevious RecordPrevious widgetPrintRelation Entries ShortcutsReport BugSaveSave AsSave As...Save Width/HeightSave this recordSearchSelect your actionSelectionSpell CheckingSubject:SwitchText Entries ShortcutsThe same bug was already reported by another user. To keep you informed your username is added to the nosy-list of this issueTo:Translate viewTrueUnable to set locale %sUnknownUser name:User:View _Logs...What is the name of this export?Width:WizardWrite AnywayYYour selection:_Actions..._Add_Cancel_Close Tab_Delete..._Duplicate_Export Data..._Import Data..._New_Next_Previous_Print..._Reload/Undo_Remove_Save_Switch Viewdhlogging everything at INFO levelmspecify alternate config filespecify the login userwProject-Id-Version: PROJECT VERSION Report-Msgid-Bugs-To: EMAIL@ADDRESS POT-Creation-Date: 2025-04-26 17:11+0200 PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE Last-Translator: FULL NAME Language: ja_JP Language-Team: ja_JP Plural-Forms: nplurals=1; plural=0; MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: 8bit Generated-By: Babel 2.17.0 中 :全てのフィールド定義済みエキスポート実行追加フィールド名を追加(_F)レコードに添付を追加する全てのファイル常にこの警告を無視する。このレコードを削除しますか?このレコードを削除しますか?添付(%d)添付:本文:バグトラッカーCC:CSVパラメータ接続(_N)Trytonサーバへの接続を中止するクリアタブを閉じるコマンドライン:比較並列性例外Trytonサーバーへ接続する選択されたテキストをコピーレコードの新規作成新しいリレーションを作る新しいバグがID番号と一緒に作成されました作成日:作成ユーザー:選択されたテキストを切り取りデータベース:削除個人設定の編集エディションウィジェットEメールEmailプログラムの設定エンコーディング:エラーCSVファイルを開くときにエラーが起こりましたエラー:例外:偽フィールド名インポートするファイル:検索高さ:ID:画像サイズ画像キーボードショートカット最終更新日:最終更新ユーザー:利用できるプレースホルダーの凡例:リミット:スキップする行:リンクログイン月モデル:名前新規(_N)次次のレコード次のウィジェット開く開くリレーションを開く/検索PNG画像 (*.png)パスワード:コピーされたテキストを貼り付け設定設定前前のレコード前のウィジェット印刷リレーションエントリショートカットバグ報告保存名前をつけて保存名前を付けて保存幅/高さを保存レコードの保存検索動作を選択する選択スペルチェック題名:切替テキストエントリショートカット別のユーザーに同じバグが報告されています。 お知らせするためにこの問題の通知リストにあなたのユーザー名を追加しました。宛先:翻訳ビュー真%s ロケールに設定できません未知ユーザー名:ユーザー:ログの閲覧(_L)このエクスポートの名前は何ですか?幅:ウィザードとにかく書き込む年あなたの選択:実行(_A)追加(_A)キャンセル(_C)タブを閉じる(_C)削除(_D)複製(_D)データのエクスポート(_E)データのインポート(_I)新規(_N)次(_N)前(_P)印刷(_P)再読み込み/やり直し(_R)削除(_R)保存(_S)ビューの切替(_S)日時全てをINFOレベルでログを取る分代替設定ファイルを指定ログインユーザーを指定週././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685599.0 tryton-7.0.24/tryton/data/locale/ja_JP/LC_MESSAGES/tryton.po0000644000175000017500000010760014517761237021452 0ustar00cedced# Japanese (Japan) translations for tryton. # Copyright (C) 2010 B2CK # This file is distributed under the same license as the tryton project. # FIRST AUTHOR , 2010. msgid "" msgstr "Content-Type: text/plain; charset=utf-8\n" #: tryton/config.py:74 msgid "specify alternate config file" msgstr "代替設定ファイルを指定" #: tryton/config.py:77 msgid "development mode" msgstr "" #: tryton/config.py:80 msgid "logging everything at INFO level" msgstr "全てをINFOレベルでログを取る" #: tryton/config.py:82 msgid "specify the log level: DEBUG, INFO, WARNING, ERROR, CRITICAL" msgstr "" #: tryton/config.py:85 msgid "specify the login user" msgstr "ログインユーザーを指定" #: tryton/config.py:87 #, fuzzy msgid "specify the server hostname:port" msgstr "サーバーホスト名を指定" #: tryton/config.py:127 #, python-format, python-format, fuzzy, python-format msgid "Unable to write config file %s." msgstr "設定ファイル %s が書きこめません!" #: tryton/translate.py:185 #, python-format msgid "Unable to set locale %s" msgstr "%s ロケールに設定できません" #: tryton/action/main.py:89 tryton/common/button.py:54 #, fuzzy msgid ", " msgstr ", " #: tryton/action/main.py:91 #, fuzzy msgid ",…" msgstr ",…" #: tryton/action/main.py:92 #, python-format msgid "%s (%s)" msgstr "" #: tryton/action/main.py:187 msgid "Select your action" msgstr "動作を選択する" #: tryton/action/main.py:193 #, fuzzy msgid "No action defined." msgstr "動作が定義されていません!" #: tryton/common/button.py:54 msgid "By: " msgstr "" #: tryton/common/common.py:307 tryton/gui/window/shortcuts.py:64 msgid "Selection" msgstr "選択" #: tryton/common/common.py:309 tryton/common/common.py:365 #: tryton/common/common.py:368 tryton/common/common.py:642 #: tryton/common/common.py:693 tryton/common/common.py:796 #: tryton/gui/window/email.py:23 tryton/gui/window/limit.py:24 #: tryton/gui/window/preference.py:35 tryton/gui/window/revision.py:25 #: tryton/gui/window/view_form/view/form_gtk/widget.py:149 #: tryton/gui/window/view_form/view/graph.py:106 #: tryton/gui/window/win_csv.py:173 tryton/gui/window/win_form.py:68 #: tryton/gui/window/win_search.py:54 #, fuzzy msgid "Cancel" msgstr "キャンセル(_C)" #: tryton/common/common.py:310 tryton/common/common.py:797 #: tryton/gui/window/email.py:28 tryton/gui/window/limit.py:29 #: tryton/gui/window/preference.py:40 tryton/gui/window/revision.py:30 #: tryton/gui/window/shortcuts.py:25 #: tryton/gui/window/view_form/view/form_gtk/widget.py:154 #: tryton/gui/window/view_form/view/graph.py:111 #: tryton/gui/window/win_csv.py:178 tryton/gui/window/win_form.py:97 #: tryton/gui/window/win_search.py:72 msgid "OK" msgstr "" #: tryton/common/common.py:315 msgid "Your selection:" msgstr "あなたの選択:" #: tryton/common/common.py:366 tryton/gui/window/form.py:114 #: tryton/gui/window/view_form/view/form_gtk/binary.py:83 msgid "Select" msgstr "" #: tryton/common/common.py:369 tryton/gui/window/win_export.py:83 msgid "Save" msgstr "保存" #: tryton/common/common.py:447 #: tryton/gui/window/view_form/view/form_gtk/binary.py:31 #: tryton/gui/window/view_form/view/form_gtk/binary.py:116 #: tryton/gui/window/view_form/view/graph.py:171 #: tryton/gui/window/view_form/view/list_gtk/widget.py:493 #: tryton/gui/window/win_export.py:305 msgid "Save As..." msgstr "名前を付けて保存" #: tryton/common/common.py:592 msgid "Always ignore this warning." msgstr "常にこの警告を無視する。" #: tryton/common/common.py:597 msgid "Do you want to proceed?" msgstr "" #: tryton/common/common.py:643 msgid "No" msgstr "" #: tryton/common/common.py:644 tryton/common/domain_parser.py:239 msgid "Yes" msgstr "" #: tryton/common/common.py:689 tryton/common/common.py:983 msgid "Concurrency Exception" msgstr "並列性例外" #: tryton/common/common.py:691 msgid "This record has been modified while you were editing it." msgstr "" #: tryton/common/common.py:694 msgid "Cancel saving" msgstr "" #: tryton/common/common.py:696 msgid "Compare" msgstr "比較" #: tryton/common/common.py:697 msgid "See the modified version" msgstr "" #: tryton/common/common.py:699 msgid "Write Anyway" msgstr "とにかく書き込む" #: tryton/common/common.py:700 msgid "Save your current version" msgstr "" #: tryton/common/common.py:729 #, fuzzy msgid "Application Error" msgstr "アプリケーションエラー!" #: tryton/common/common.py:732 msgid "Report Bug" msgstr "バグ報告" #: tryton/common/common.py:733 tryton/gui/window/dblogin.py:36 msgid "Close" msgstr "" #: tryton/common/common.py:751 msgid "Error: " msgstr "エラー:" #: tryton/common/common.py:768 #, python-format, fuzzy msgid "To report bugs you must have an account on %s" msgstr "記録しておきたいバグを報告%s" #: tryton/common/common.py:794 msgid "Bug Tracker" msgstr "バグトラッカー" #: tryton/common/common.py:811 msgid "User:" msgstr "ユーザー:" #: tryton/common/common.py:819 msgid "Password:" msgstr "パスワード:" #: tryton/common/common.py:875 msgid "" "The same bug was already reported by another user.\n" "To keep you informed your username is added to the nosy-list of this issue" msgstr "" "別のユーザーに同じバグが報告されています。\n" "お知らせするためにこの問題の通知リストにあなたのユーザー名を追加しました。" #: tryton/common/common.py:886 msgid "Created new bug with ID " msgstr "新しいバグがID番号と一緒に作成されました" #: tryton/common/common.py:894 #, fuzzy msgid "" "Connection error.\n" "Bad username or password." msgstr "" "接続エラー!\n" "ユーザー名またはパスワードが違います!" #: tryton/common/common.py:899 msgid "Exception:" msgstr "例外:" #: tryton/common/common.py:926 #, python-format msgid "Check URL: %s" msgstr "" #: tryton/common/common.py:934 msgid "Unable to check for new version" msgstr "" #: tryton/common/common.py:940 msgid "A new version is available!" msgstr "" #: tryton/common/common.py:942 msgid "Download" msgstr "" #: tryton/common/common.py:1323 #, fuzzy msgid "..." msgstr "..." #: tryton/common/completion.py:25 msgid "Search..." msgstr "" #: tryton/common/completion.py:27 msgid "Create..." msgstr "" #: tryton/common/completion.py:70 #, python-format msgid "Unable to search for completion of %s" msgstr "" #: tryton/common/datetime_.py:32 tryton/common/datetime_.py:238 #: tryton/common/datetime_.py:391 #, fuzzy msgid "Value" msgstr "偽" #: tryton/common/datetime_.py:33 tryton/common/datetime_.py:239 #: tryton/common/datetime_.py:392 msgid "Displayed value" msgstr "" #: tryton/common/datetime_.py:37 tryton/common/datetime_.py:195 #: tryton/common/datetime_.py:242 tryton/common/datetime_.py:347 #, fuzzy msgid "Format" msgstr "ノーマル(_N)" #: tryton/common/datetime_.py:38 tryton/common/datetime_.py:196 #: tryton/common/datetime_.py:243 tryton/common/datetime_.py:348 msgid "Display format" msgstr "" #: tryton/common/datetime_.py:63 #, fuzzy msgid "Open the calendar" msgstr "カレンダーを開く" #: tryton/common/datetime_.py:396 tryton/common/datetime_.py:401 msgid "Date Format" msgstr "" #: tryton/common/datetime_.py:397 tryton/common/datetime_.py:402 msgid "Displayed date format" msgstr "" #: tryton/common/domain_parser.py:239 msgid "y" msgstr "" #: tryton/common/domain_parser.py:239 tryton/common/domain_parser.py:490 #: tryton/gui/window/view_form/view/screen_container.py:567 msgid "True" msgstr "真" #: tryton/common/domain_parser.py:239 msgid "t" msgstr "" #: tryton/common/domain_parser.py:490 #: tryton/gui/window/view_form/view/screen_container.py:567 msgid "False" msgstr "偽" #: tryton/common/popup_menu.py:84 #, fuzzy msgid "Edit..." msgstr "終了(_Q)" #: tryton/common/popup_menu.py:89 #, fuzzy msgid "Attachments..." msgstr "添付:" #: tryton/common/popup_menu.py:95 msgid "Notes..." msgstr "" #: tryton/common/popup_menu.py:107 #, fuzzy msgid "Actions..." msgstr "実行(_A)" #: tryton/common/popup_menu.py:108 #, fuzzy msgid "Relate..." msgstr "削除(_D)" #: tryton/common/popup_menu.py:109 #, fuzzy msgid "Report..." msgstr "データのインポート(_I)" #: tryton/common/popup_menu.py:110 #, fuzzy msgid "E-Mail..." msgstr "Eメール(_E)" #: tryton/common/popup_menu.py:111 #, fuzzy msgid "Print..." msgstr "印刷(_P)" #: tryton/common/timedelta.py:26 msgid "Y" msgstr "年" #: tryton/common/timedelta.py:27 msgid "M" msgstr "月" #: tryton/common/timedelta.py:28 msgid "w" msgstr "週" #: tryton/common/timedelta.py:29 msgid "d" msgstr "日" #: tryton/common/timedelta.py:30 msgid "h" msgstr "時" #: tryton/common/timedelta.py:31 msgid "m" msgstr "分" #: tryton/common/timedelta.py:32 msgid "s" msgstr "" #: tryton/gui/main.py:123 #, fuzzy msgid "Preferences..." msgstr "設定(_P)" #: tryton/gui/main.py:127 #, fuzzy msgid "Toolbar" msgstr "ツールバー(_T)" #: tryton/gui/main.py:128 #, fuzzy msgid "Default" msgstr "デフォルト(_D)" #: tryton/gui/main.py:129 #, fuzzy msgid "Text and Icons" msgstr "テキストとアイコン(_T)" #: tryton/gui/main.py:130 #, fuzzy msgid "Text" msgstr "テキスト(_T)" #: tryton/gui/main.py:131 #, fuzzy msgid "Icons" msgstr "アイコン(_I)" #: tryton/gui/main.py:134 #, fuzzy msgid "Form" msgstr "フォーム(_F)" #: tryton/gui/main.py:135 msgid "Save Width/Height" msgstr "幅/高さを保存" #: tryton/gui/main.py:136 msgid "Save Tree State" msgstr "" #: tryton/gui/main.py:137 msgid "Fast Tabbing" msgstr "" #: tryton/gui/main.py:138 msgid "Spell Checking" msgstr "スペルチェック" #: tryton/gui/main.py:140 msgid "PDA Mode" msgstr "" #: tryton/gui/main.py:141 msgid "Search Limit..." msgstr "" #: tryton/gui/main.py:142 #, fuzzy msgid "Email..." msgstr "Eメール(_E)" #: tryton/gui/main.py:143 msgid "Check Version" msgstr "" #: tryton/gui/main.py:145 #, fuzzy msgid "Options" msgstr "オプション(_O)" #: tryton/gui/main.py:148 #, fuzzy msgid "Keyboard Shortcuts..." msgstr "キーボードショートカット(_K)" #: tryton/gui/main.py:149 #, fuzzy msgid "About..." msgstr "Trytonについて" #: tryton/gui/main.py:150 #, fuzzy msgid "Help" msgstr "ヘルプ(_H)" #: tryton/gui/main.py:153 msgid "Quit" msgstr "" #: tryton/gui/main.py:395 msgid "No result found." msgstr "" #: tryton/gui/main.py:447 msgid "Favorites" msgstr "" #: tryton/gui/main.py:464 tryton/gui/window/dblogin.py:438 #: tryton/gui/window/form.py:139 msgid "Manage..." msgstr "" #: tryton/gui/main.py:541 tryton/gui/window/form.py:559 msgid "Action" msgstr "実行" #: tryton/gui/main.py:563 msgid "" "The following action requires to close all tabs.\n" "Do you want to continue?" msgstr "" #: tryton/gui/main.py:747 msgid "Close Tab" msgstr "タブを閉じる" #: tryton/gui/window/about.py:37 msgid "modularity, scalability and security" msgstr "" #: tryton/gui/window/about.py:43 msgid "translator-credits" msgstr "" #: tryton/gui/window/attachment.py:24 #, python-format, python-format, fuzzy, python-format msgid "Attachments (%s)" msgstr "添付(%s)" #: tryton/gui/window/dblogin.py:33 msgid "Profile Editor" msgstr "" #: tryton/gui/window/dblogin.py:49 msgid "Profile" msgstr "" #: tryton/gui/window/dblogin.py:58 msgid "Add new profile" msgstr "" #: tryton/gui/window/dblogin.py:63 msgid "Remove selected profile" msgstr "" #: tryton/gui/window/dblogin.py:77 tryton/gui/window/dblogin.py:450 #, fuzzy msgid "Host:" msgstr "ポート番号:" #: tryton/gui/window/dblogin.py:88 tryton/gui/window/dblogin.py:462 msgid "Database:" msgstr "データベース:" #: tryton/gui/window/dblogin.py:108 #, fuzzy msgid "Fetching databases list" msgstr "新規データベースセットアップ:" #: tryton/gui/window/dblogin.py:122 #, fuzzy msgid "Username:" msgstr "ユーザー名:" #: tryton/gui/window/dblogin.py:309 tryton/gui/window/dblogin.py:619 #, fuzzy msgid "Incompatible version of the server" msgstr "サーバのバージョンが非互換です!" #: tryton/gui/window/dblogin.py:311 tryton/gui/window/dblogin.py:623 #, fuzzy msgid "Could not connect to the server" msgstr "サーバーに接続できませんでした!" #: tryton/gui/window/dblogin.py:390 msgid "Login" msgstr "ログイン" #: tryton/gui/window/dblogin.py:396 msgid "_Cancel" msgstr "キャンセル(_C)" #: tryton/gui/window/dblogin.py:398 msgid "Cancel connection to the Tryton server" msgstr "Trytonサーバへの接続を中止する" #: tryton/gui/window/dblogin.py:400 msgid "C_onnect" msgstr "接続(_N)" #: tryton/gui/window/dblogin.py:404 msgid "Connect the Tryton server" msgstr "Trytonサーバーへ接続する" #: tryton/gui/window/dblogin.py:432 msgid "Profile:" msgstr "" #: tryton/gui/window/dblogin.py:447 msgid "Host / Database information" msgstr "" #: tryton/gui/window/dblogin.py:478 msgid "User name:" msgstr "ユーザー名:" #: tryton/gui/window/email.py:19 msgid "Email" msgstr "Eメール" #: tryton/gui/window/email.py:36 msgid "Email Program Settings" msgstr "Emailプログラムの設定" #: tryton/gui/window/email.py:39 msgid "Command Line:" msgstr "コマンドライン:" #: tryton/gui/window/email.py:49 msgid "Legend of Available Placeholders:" msgstr "利用できるプレースホルダーの凡例:" #: tryton/gui/window/email.py:56 msgid "To:" msgstr "宛先:" #: tryton/gui/window/email.py:60 msgid "CC:" msgstr "CC:" #: tryton/gui/window/email.py:64 msgid "Subject:" msgstr "題名:" #: tryton/gui/window/email.py:68 msgid "Body:" msgstr "本文:" #: tryton/gui/window/email.py:72 msgid "Attachment:" msgstr "添付:" #: tryton/gui/window/form.py:135 msgid "Add..." msgstr "" #: tryton/gui/window/form.py:155 #, python-format msgid "Attachment(%d)" msgstr "添付(%d)" #: tryton/gui/window/form.py:185 #, python-format msgid "Note(%d)" msgstr "" #: tryton/gui/window/form.py:207 #, fuzzy msgid "You have to select one record." msgstr "レコードを一つ選択して下さい!" #: tryton/gui/window/form.py:211 msgid "ID:" msgstr "ID:" #: tryton/gui/window/form.py:212 msgid "Creation User:" msgstr "作成ユーザー:" #: tryton/gui/window/form.py:213 msgid "Creation Date:" msgstr "作成日:" #: tryton/gui/window/form.py:214 msgid "Latest Modification by:" msgstr "最終更新ユーザー:" #: tryton/gui/window/form.py:215 msgid "Latest Modification Date:" msgstr "最終更新日:" #: tryton/gui/window/form.py:234 msgid "Model:" msgstr "モデル:" #: tryton/gui/window/form.py:312 msgid "Are you sure to remove this record?" msgstr "このレコードを削除しますか?" #: tryton/gui/window/form.py:314 msgid "Are you sure to remove those records?" msgstr "このレコードを削除しますか?" #: tryton/gui/window/form.py:317 #, fuzzy msgid "Records not removed." msgstr "レコードが削除されませんでした!" #: tryton/gui/window/form.py:319 #, fuzzy msgid "Records removed." msgstr "レコードが削除されました!" #: tryton/gui/window/form.py:356 #, fuzzy msgid "Working now on the duplicated record(s)." msgstr "重複したレコードで作業中です!" #: tryton/gui/window/form.py:368 #, fuzzy msgid "Record saved." msgstr "レコードが保存されました!" #: tryton/gui/window/form.py:485 msgid " of " msgstr " 中 " #: tryton/gui/window/form.py:505 #, fuzzy msgid "" "This record has been modified\n" "do you want to save it?" msgstr "" "レコードが変更されています\n" "保存しますか?" #: tryton/gui/window/form.py:559 msgid "Launch action" msgstr "" #: tryton/gui/window/form.py:560 #, fuzzy msgid "Relate" msgstr "作成(_C)" #: tryton/gui/window/form.py:560 #, fuzzy msgid "Open related records" msgstr "レコードを開く" #: tryton/gui/window/form.py:562 #, fuzzy msgid "Report" msgstr "レポート" #: tryton/gui/window/form.py:562 #, fuzzy msgid "Open report" msgstr "レコードを開く" #: tryton/gui/window/form.py:563 #, fuzzy msgid "E-Mail" msgstr "Eメール" #: tryton/gui/window/form.py:563 msgid "E-Mail report" msgstr "" #: tryton/gui/window/form.py:564 msgid "Print" msgstr "印刷" #: tryton/gui/window/form.py:564 msgid "Print report" msgstr "" #: tryton/gui/window/form.py:590 msgid "_Copy URL" msgstr "" #: tryton/gui/window/form.py:593 msgid "Copy URL into clipboard" msgstr "" #: tryton/gui/window/form.py:652 #: tryton/gui/window/view_form/view/list_gtk/widget.py:932 msgid "Unknown" msgstr "未知" #: tryton/gui/window/limit.py:20 #, fuzzy msgid "Limit" msgstr "リミット:" #: tryton/gui/window/limit.py:37 msgid "Search Limit Settings" msgstr "" #: tryton/gui/window/limit.py:40 msgid "Limit:" msgstr "リミット:" #: tryton/gui/window/note.py:17 #, python-format msgid "Notes (%s)" msgstr "" #: tryton/gui/window/preference.py:25 msgid "Preferences" msgstr "設定" #: tryton/gui/window/preference.py:58 msgid "Edit User Preferences" msgstr "個人設定の編集" #: tryton/gui/window/preference.py:85 msgid "Preference" msgstr "設定" #: tryton/gui/window/revision.py:21 #, fuzzy msgid "Revision" msgstr "前" #: tryton/gui/window/revision.py:38 #, fuzzy msgid "Select a revision" msgstr "選択" #: tryton/gui/window/revision.py:41 #, fuzzy msgid "Revision:" msgstr "前" #: tryton/gui/window/shortcuts.py:20 msgid "Keyboard Shortcuts" msgstr "キーボードショートカット" #: tryton/gui/window/shortcuts.py:35 msgid "Text Entries Shortcuts" msgstr "テキストエントリショートカット" #: tryton/gui/window/shortcuts.py:36 msgid "Cut selected text" msgstr "選択されたテキストを切り取り" #: tryton/gui/window/shortcuts.py:37 msgid "Copy selected text" msgstr "選択されたテキストをコピー" #: tryton/gui/window/shortcuts.py:38 msgid "Paste copied text" msgstr "コピーされたテキストを貼り付け" #: tryton/gui/window/shortcuts.py:39 msgid "Next widget" msgstr "次のウィジェット" #: tryton/gui/window/shortcuts.py:40 msgid "Previous widget" msgstr "前のウィジェット" #: tryton/gui/window/shortcuts.py:41 msgid "Relation Entries Shortcuts" msgstr "リレーションエントリショートカット" #: tryton/gui/window/shortcuts.py:42 msgid "Create new relation" msgstr "新しいリレーションを作る" #: tryton/gui/window/shortcuts.py:43 msgid "Open/Search relation" msgstr "リレーションを開く/検索" #: tryton/gui/window/shortcuts.py:44 #, fuzzy msgid "List Entries Shortcuts" msgstr "テキストエントリショートカット" #: tryton/gui/window/shortcuts.py:45 #, fuzzy msgid "Create new line" msgstr "新しいリレーションを作る" #: tryton/gui/window/shortcuts.py:46 #, fuzzy msgid "Open relation" msgstr "リレーションを開く/検索" #: tryton/gui/window/shortcuts.py:47 msgid "Mark line for deletion" msgstr "" #: tryton/gui/window/shortcuts.py:48 msgid "Unmark line for deletion" msgstr "" #: tryton/gui/window/shortcuts.py:51 msgid "Edition Widgets" msgstr "エディションウィジェット" #: tryton/gui/window/shortcuts.py:54 msgid "Move Cursor" msgstr "" #: tryton/gui/window/shortcuts.py:55 msgid "Move to right" msgstr "" #: tryton/gui/window/shortcuts.py:56 msgid "Move to left" msgstr "" #: tryton/gui/window/shortcuts.py:57 msgid "Move up" msgstr "" #: tryton/gui/window/shortcuts.py:58 msgid "Move down" msgstr "" #: tryton/gui/window/shortcuts.py:59 msgid "Move up of one page" msgstr "" #: tryton/gui/window/shortcuts.py:60 msgid "Move down of one page" msgstr "" #: tryton/gui/window/shortcuts.py:61 msgid "Move to top" msgstr "" #: tryton/gui/window/shortcuts.py:62 msgid "Move to bottom" msgstr "" #: tryton/gui/window/shortcuts.py:63 msgid "Move to parent" msgstr "" #: tryton/gui/window/shortcuts.py:65 tryton/gui/window/shortcuts.py:66 msgid "Select all" msgstr "" #: tryton/gui/window/shortcuts.py:67 tryton/gui/window/shortcuts.py:68 msgid "Unselect all" msgstr "" #: tryton/gui/window/shortcuts.py:69 msgid "Select parent" msgstr "" #: tryton/gui/window/shortcuts.py:70 tryton/gui/window/shortcuts.py:71 #: tryton/gui/window/shortcuts.py:72 tryton/gui/window/shortcuts.py:73 msgid "Select/Activate current row" msgstr "" #: tryton/gui/window/shortcuts.py:74 msgid "Toggle selection" msgstr "" #: tryton/gui/window/shortcuts.py:75 msgid "Expand/Collapse" msgstr "" #: tryton/gui/window/shortcuts.py:76 msgid "Expand row" msgstr "" #: tryton/gui/window/shortcuts.py:77 msgid "Collapse row" msgstr "" #: tryton/gui/window/shortcuts.py:78 msgid "Toggle row" msgstr "" #: tryton/gui/window/shortcuts.py:79 msgid "Collapse all rows" msgstr "" #: tryton/gui/window/shortcuts.py:80 msgid "Expand all rows" msgstr "" #: tryton/gui/window/shortcuts.py:83 msgid "Tree view" msgstr "" #: tryton/gui/window/tabcontent.py:43 msgid "_Switch View" msgstr "ビューの切替(_S)" #: tryton/gui/window/tabcontent.py:44 #, fuzzy msgid "Switch View" msgstr "ビューの切替(_S)" #: tryton/gui/window/tabcontent.py:49 msgid "_Previous" msgstr "前(_P)" #: tryton/gui/window/tabcontent.py:50 msgid "Previous Record" msgstr "前のレコード" #: tryton/gui/window/tabcontent.py:55 msgid "_Next" msgstr "次(_N)" #: tryton/gui/window/tabcontent.py:56 msgid "Next Record" msgstr "次のレコード" #: tryton/gui/window/tabcontent.py:61 #, fuzzy msgid "_Search" msgstr "検索" #: tryton/gui/window/tabcontent.py:67 msgid "_New" msgstr "新規(_N)" #: tryton/gui/window/tabcontent.py:68 msgid "Create a new record" msgstr "レコードの新規作成" #: tryton/gui/window/tabcontent.py:73 tryton/gui/window/win_form.py:85 msgid "_Save" msgstr "保存(_S)" #: tryton/gui/window/tabcontent.py:74 msgid "Save this record" msgstr "レコードの保存" #: tryton/gui/window/tabcontent.py:79 msgid "_Reload/Undo" msgstr "再読み込み/やり直し(_R)" #: tryton/gui/window/tabcontent.py:80 #, fuzzy msgid "Reload/Undo" msgstr "再読み込み/やり直し(_R)" #: tryton/gui/window/tabcontent.py:85 msgid "_Duplicate" msgstr "複製(_D)" #: tryton/gui/window/tabcontent.py:90 msgid "_Delete..." msgstr "削除(_D)" #: tryton/gui/window/tabcontent.py:96 msgid "View _Logs..." msgstr "ログの閲覧(_L)" #: tryton/gui/window/tabcontent.py:100 msgid "Show revisions..." msgstr "" #: tryton/gui/window/tabcontent.py:105 #, fuzzy msgid "A_ttachments..." msgstr "添付:" #: tryton/gui/window/tabcontent.py:106 msgid "Add an attachment to the record" msgstr "レコードに添付を追加する" #: tryton/gui/window/tabcontent.py:112 msgid "_Notes..." msgstr "" #: tryton/gui/window/tabcontent.py:113 msgid "Add a note to the record" msgstr "" #: tryton/gui/window/tabcontent.py:118 msgid "_Actions..." msgstr "実行(_A)" #: tryton/gui/window/tabcontent.py:123 #, fuzzy msgid "_Relate..." msgstr "削除(_D)" #: tryton/gui/window/tabcontent.py:129 #, fuzzy msgid "_Report..." msgstr "データのインポート(_I)" #: tryton/gui/window/tabcontent.py:134 #, fuzzy msgid "_E-Mail..." msgstr "Eメール(_E)" #: tryton/gui/window/tabcontent.py:139 msgid "_Print..." msgstr "印刷(_P)" #: tryton/gui/window/tabcontent.py:145 msgid "_Export Data..." msgstr "データのエクスポート(_E)" #: tryton/gui/window/tabcontent.py:150 msgid "_Import Data..." msgstr "データのインポート(_I)" #: tryton/gui/window/tabcontent.py:155 msgid "Copy _URL..." msgstr "" #: tryton/gui/window/tabcontent.py:161 msgid "_Close Tab" msgstr "タブを閉じる(_C)" #: tryton/gui/window/win_csv.py:60 msgid "All fields" msgstr "全てのフィールド" #: tryton/gui/window/win_csv.py:69 msgid "_Add" msgstr "追加(_A)" #: tryton/gui/window/win_csv.py:77 msgid "_Remove" msgstr "削除(_R)" #: tryton/gui/window/win_csv.py:85 #, fuzzy msgid "_Clear" msgstr "クリア" #: tryton/gui/window/win_csv.py:105 msgid "Fields selected" msgstr "" #: tryton/gui/window/win_csv.py:124 msgid "CSV Parameters" msgstr "CSVパラメータ" #: tryton/gui/window/win_csv.py:132 msgid "Delimiter:" msgstr "" #: tryton/gui/window/win_csv.py:146 msgid "Quote char:" msgstr "" #: tryton/gui/window/win_csv.py:155 msgid "Encoding:" msgstr "エンコーディング:" #: tryton/gui/window/win_csv.py:195 tryton/gui/window/win_csv.py:199 msgid "Field name" msgstr "フィールド名" #: tryton/gui/window/win_export.py:28 #, python-format msgid "CSV Export: %s" msgstr "" #: tryton/gui/window/win_export.py:32 #, fuzzy msgid "_Save Export" msgstr "エクスポートを保存" #: tryton/gui/window/win_export.py:40 #, fuzzy msgid "_Delete Export" msgstr "エクスポートを削除" #: tryton/gui/window/win_export.py:55 msgid "Predefined exports" msgstr "定義済みエキスポート" #: tryton/gui/window/win_export.py:62 msgid "Name" msgstr "名前" #: tryton/gui/window/win_export.py:82 msgid "Open" msgstr "開く" #: tryton/gui/window/win_export.py:87 msgid "Add _field names" msgstr "フィールド名を追加(_F)" #: tryton/gui/window/win_export.py:103 #, python-format msgid "%s (string)" msgstr "" #: tryton/gui/window/win_export.py:106 #, python-format msgid "%s (model name)" msgstr "" #: tryton/gui/window/win_export.py:108 #, python-format msgid "%s (record name)" msgstr "" #: tryton/gui/window/win_export.py:207 msgid "What is the name of this export?" msgstr "このエクスポートの名前は何ですか?" #: tryton/gui/window/win_export.py:213 #, python-format msgid "Override '%s' definition?" msgstr "" #: tryton/gui/window/win_export.py:328 #, python-format, python-format, fuzzy, python-format msgid "%d record saved." msgstr "%d レコード保存されました!" #: tryton/gui/window/win_export.py:330 #, python-format, python-format, fuzzy, python-format msgid "%d records saved." msgstr "%d レコード保存されました!" #: tryton/gui/window/win_export.py:333 #, python-format, python-format, fuzzy, python-format msgid "" "Operation failed.\n" "Error message:\n" "%s" msgstr "" "操作が失敗しました!\n" "エラーメッセージ:\n" "%s" #: tryton/gui/window/win_export.py:334 tryton/gui/window/win_import.py:112 #: tryton/gui/window/win_import.py:139 msgid "Error" msgstr "エラー" #: tryton/gui/window/win_form.py:38 msgid "Link" msgstr "リンク" #: tryton/gui/window/win_form.py:66 msgid "Delete" msgstr "削除" #: tryton/gui/window/win_form.py:78 tryton/gui/window/win_search.py:65 msgid "New" msgstr "新規(_N)" #: tryton/gui/window/view_form/view/form_gtk/one2many.py:48 #: tryton/gui/window/win_form.py:140 msgid "Switch" msgstr "切替" #: tryton/gui/window/view_form/view/form_gtk/one2many.py:56 #: tryton/gui/window/view_form/view/screen_container.py:222 #: tryton/gui/window/win_form.py:148 msgid "Previous" msgstr "前" #: tryton/gui/window/view_form/view/form_gtk/one2many.py:67 #: tryton/gui/window/view_form/view/screen_container.py:231 #: tryton/gui/window/win_form.py:159 msgid "Next" msgstr "次" #: tryton/gui/window/win_form.py:176 msgid "Add" msgstr "追加" #: tryton/gui/window/win_form.py:186 msgid "Remove " msgstr "" #: tryton/gui/window/view_form/view/form_gtk/one2many.py:118 #: tryton/gui/window/win_form.py:198 #, fuzzy msgid "Create a new record " msgstr "レコードの新規作成" #: tryton/gui/window/view_form/view/form_gtk/one2many.py:134 #: tryton/gui/window/win_form.py:208 #, fuzzy msgid "Delete selected record " msgstr "選択したレコードを削除" #: tryton/gui/window/view_form/view/form_gtk/one2many.py:142 #: tryton/gui/window/win_form.py:219 #, fuzzy msgid "Undelete selected record " msgstr "選択したレコードを削除" #: tryton/gui/window/win_import.py:26 #, python-format msgid "CSV Import: %s" msgstr "" #: tryton/gui/window/win_import.py:30 #, fuzzy msgid "_Auto-Detect" msgstr "自動検出" #: tryton/gui/window/win_import.py:41 msgid "File to Import:" msgstr "インポートするファイル:" #: tryton/gui/window/view_form/view/form_gtk/binary.py:200 #: tryton/gui/window/view_form/view/list_gtk/widget.py:466 #: tryton/gui/window/win_import.py:43 msgid "Open..." msgstr "開く" #: tryton/gui/window/win_import.py:48 msgid "Lines to Skip:" msgstr "スキップする行:" #: tryton/gui/window/win_import.py:101 #, fuzzy msgid "You must select an import file first." msgstr "最初にインポートするファイルを選択して下さい!" #: tryton/gui/window/win_import.py:112 msgid "Error opening CSV file" msgstr "CSVファイルを開くときにエラーが起こりました" #: tryton/gui/window/win_import.py:138 #, python-format, python-format, fuzzy, python-format msgid "Error processing the file at field %s." msgstr "ファイルの%sフィールドの処理でエラーが起こりました。" #: tryton/gui/window/win_import.py:198 #, python-format, python-format, fuzzy, python-format msgid "%d record imported." msgstr "%d レコードをインポートしました!" #: tryton/gui/window/win_import.py:200 #, python-format, python-format, fuzzy, python-format msgid "%d records imported." msgstr "%d レコードをインポートしました。" #: tryton/gui/window/view_form/view/form_gtk/dictionary.py:344 #: tryton/gui/window/view_form/view/form_gtk/many2many.py:46 #: tryton/gui/window/view_form/view/form_gtk/one2many.py:81 #: tryton/gui/window/view_form/view/screen_container.py:146 #: tryton/gui/window/win_search.py:36 tryton/gui/window/win_search.py:59 msgid "Search" msgstr "検索" #: tryton/gui/window/win_search.py:91 #, python-format msgid "Search %s" msgstr "" #: tryton/gui/window/wizard.py:160 #, python-format msgid "Unable to delete wizard %s" msgstr "" #: tryton/gui/window/wizard.py:317 msgid "Wizard" msgstr "ウィザード" #: tryton/gui/window/view_form/screen/screen.py:206 #, fuzzy msgid "ID" msgstr "日" #: tryton/gui/window/view_form/screen/screen.py:207 #, fuzzy msgid "Creation User" msgstr "作成ユーザー:" #: tryton/gui/window/view_form/screen/screen.py:208 #, fuzzy msgid "Creation Date" msgstr "作成日:" #: tryton/gui/window/view_form/screen/screen.py:209 #, fuzzy msgid "Modification User" msgstr "最終更新日:" #: tryton/gui/window/view_form/screen/screen.py:210 #, fuzzy msgid "Modification Date" msgstr "最終更新日:" #: tryton/gui/window/view_form/screen/screen.py:806 #, python-format, python-format, fuzzy msgid "Unable to get view tree state for %s" msgstr "%s ロケールに設定できません" #: tryton/gui/window/view_form/screen/screen.py:867 #, fuzzy msgid "Unable to set view tree state" msgstr "%s ロケールに設定できません" #: tryton/gui/window/view_form/screen/screen.py:1054 #, python-format msgid "\"%s\" is not valid according to its domain" msgstr "" #: tryton/gui/window/view_form/screen/screen.py:1061 #, python-format msgid "\"%s\" is required" msgstr "" #: tryton/gui/window/view_form/screen/screen.py:1065 #, python-format msgid "The values of \"%s\" are not valid" msgstr "" #: tryton/gui/window/view_form/screen/screen.py:1114 msgid "Pre-validation" msgstr "" #: tryton/gui/window/view_form/view/form.py:261 #: tryton/gui/window/view_form/view/form.py:263 #: tryton/gui/window/view_form/view/form_gtk/dictionary.py:476 #: tryton/gui/window/view_form/view/form_gtk/dictionary.py:478 #: tryton/gui/window/view_form/view/form_gtk/widget.py:172 #: tryton/gui/window/view_form/view/form_gtk/widget.py:174 #: tryton/gui/window/view_form/view/list.py:530 #: tryton/gui/window/view_form/view/list.py:532 msgid ":" msgstr ":" #: tryton/gui/window/view_form/view/graph.py:102 msgid "Image Size" msgstr "画像サイズ" #: tryton/gui/window/view_form/view/graph.py:121 msgid "Width:" msgstr "幅:" #: tryton/gui/window/view_form/view/graph.py:129 msgid "Height:" msgstr "高さ:" #: tryton/gui/window/view_form/view/graph.py:140 msgid "PNG image (*.png)" msgstr "PNG画像 (*.png)" #: tryton/gui/window/view_form/view/graph.py:149 msgid "Save As" msgstr "名前をつけて保存" #: tryton/gui/window/view_form/view/graph.py:161 #, fuzzy msgid "Image size too large." msgstr "画像サイズが大きすぎます!" #: tryton/gui/window/view_form/view/screen_container.py:23 msgid ".." msgstr "" #: tryton/gui/window/view_form/view/screen_container.py:152 msgid "Open filters" msgstr "" #: tryton/gui/window/view_form/view/screen_container.py:209 msgid "Show bookmarks of filters" msgstr "" #: tryton/gui/window/view_form/view/screen_container.py:371 msgid "Remove this bookmark" msgstr "" #: tryton/gui/window/view_form/view/screen_container.py:379 msgid "Bookmark this filter" msgstr "" #: tryton/gui/window/view_form/view/screen_container.py:396 msgid "Show active records" msgstr "" #: tryton/gui/window/view_form/view/screen_container.py:398 msgid "Show inactive records" msgstr "" #: tryton/gui/window/view_form/view/screen_container.py:479 msgid "Bookmark Name:" msgstr "" #: tryton/gui/window/view_form/view/screen_container.py:599 msgid "Find" msgstr "検索" #: tryton/gui/window/view_form/view/calendar_gtk/toolbar.py:22 #, fuzzy msgid "Today" msgstr "本文:" #: tryton/gui/window/view_form/view/calendar_gtk/toolbar.py:32 msgid "go back" msgstr "" #: tryton/gui/window/view_form/view/calendar_gtk/toolbar.py:73 msgid "go forward" msgstr "" #: tryton/gui/window/view_form/view/calendar_gtk/toolbar.py:82 msgid "previous year" msgstr "" #: tryton/gui/window/view_form/view/calendar_gtk/toolbar.py:96 msgid "next year" msgstr "" #: tryton/gui/window/view_form/view/calendar_gtk/toolbar.py:107 #, fuzzy msgid "Week View" msgstr "ビューの切替" #: tryton/gui/window/view_form/view/calendar_gtk/toolbar.py:117 #, fuzzy msgid "Month View" msgstr "ビューの切替" #: tryton/gui/window/view_form/view/calendar_gtk/toolbar.py:142 msgid "Week" msgstr "" #: tryton/gui/window/view_form/view/form_gtk/binary.py:39 msgid "Select..." msgstr "" #: tryton/gui/window/view_form/view/form_gtk/binary.py:47 msgid "Clear" msgstr "クリア" #: tryton/gui/window/view_form/view/form_gtk/binary.py:60 msgid "All files" msgstr "全てのファイル" #: tryton/gui/window/view_form/view/form_gtk/char.py:169 msgid "Show plain text" msgstr "" #: tryton/gui/window/view_form/view/form_gtk/dictionary.py:369 msgid "Add value" msgstr "" #: tryton/gui/window/view_form/view/form_gtk/dictionary.py:490 #, python-format msgid "Remove \"%s\"" msgstr "" #: tryton/gui/window/view_form/view/form_gtk/image.py:50 msgid "Images" msgstr "画像" #: tryton/gui/window/view_form/view/form_gtk/many2many.py:66 #: tryton/gui/window/view_form/view/form_gtk/one2many.py:99 #, fuzzy msgid "Add existing record" msgstr "レコードの保存" #: tryton/gui/window/view_form/view/form_gtk/many2many.py:74 #, fuzzy msgid "Remove selected record " msgstr "選択したレコードを削除" #: tryton/gui/window/view_form/view/form_gtk/many2one.py:305 #: tryton/gui/window/view_form/view/list_gtk/widget.py:582 #, fuzzy msgid "Open the record " msgstr "レコードを開く" #: tryton/gui/window/view_form/view/form_gtk/many2one.py:306 #: tryton/gui/window/view_form/view/list_gtk/widget.py:583 msgid "Clear the field " msgstr "" #: tryton/gui/window/view_form/view/form_gtk/many2one.py:309 #: tryton/gui/window/view_form/view/list_gtk/widget.py:586 #, fuzzy msgid "Search a record " msgstr "レコードを検索する" #: tryton/gui/window/view_form/view/form_gtk/one2many.py:108 #, fuzzy msgid "Remove selected record" msgstr "選択したレコードを削除" #: tryton/gui/window/view_form/view/form_gtk/one2many.py:126 #, fuzzy msgid "Edit selected record " msgstr "選択したレコードを編集" #: tryton/gui/window/view_form/view/form_gtk/progressbar.py:35 #: tryton/gui/window/view_form/view/list_gtk/widget.py:908 #, python-format msgid "%s%%" msgstr "" #: tryton/gui/window/view_form/view/form_gtk/richtextbox.py:85 msgid "Foreground" msgstr "" #: tryton/gui/window/view_form/view/form_gtk/richtextbox.py:328 msgid "Select a color" msgstr "" #: tryton/gui/window/view_form/view/form_gtk/widget.py:136 #, fuzzy msgid "Translation" msgstr "翻訳を追加" #: tryton/gui/window/view_form/view/form_gtk/widget.py:209 msgid "Edit" msgstr "" #: tryton/gui/window/view_form/view/form_gtk/widget.py:214 msgid "Fuzzy" msgstr "" #: tryton/gui/window/view_form/view/form_gtk/widget.py:285 #, fuzzy msgid "You need to save the record before adding translations." msgstr "翻訳を追加する前にレコードを保存して下さい!" #: tryton/gui/window/view_form/view/form_gtk/widget.py:296 #, fuzzy msgid "No other language available." msgstr "他の利用できる言語はありません" #: tryton/plugins/translation/__init__.py:17 #: tryton/plugins/translation/__init__.py:24 msgid "Translate view" msgstr "翻訳ビュー" ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1745680285.1274436 tryton-7.0.24/tryton/data/locale/lo/0000755000175000017500000000000015003173635015377 5ustar00cedced././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1745680285.1641107 tryton-7.0.24/tryton/data/locale/lo/LC_MESSAGES/0000755000175000017500000000000015003173635017164 5ustar00cedced././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1745680279.0 tryton-7.0.24/tryton/data/locale/lo/LC_MESSAGES/tryton.mo0000644000175000017500000004366715003173627021101 0ustar00cedced /F`q    #%%Io&  &.D^ v   #.=Sc {    !(.6RX[ _j      %-AFJ O[n     5Oas     . 5 ? KXo     #8?N ` ky   I#5m  >F _ l w  ( %$7J       $) / 9 C M Xe mx ~     <#`wy{}MGT\VP X p u     7 F U!t!!!$!!"Z"Zk"B" #""#FE#a#s#,b$$$0$ $O$N%mk%%%$&*3&^&Qq&O&<'P'Nd'0'5'3(?N((!( (B()*1)B\)!)U)*1*HA****-*$*=#+ a+Bk+'+7+,!,4,M,Zd,,,,-/(- X-0b---(--' .4.O. S.(^..6.!.0.-'/U/-n/ ///+/T/:M0*0000!0? 1$J1!o11&1 141G2[2Hz2(22 3*30@3q3$33*3H3+34+_494L4F5Y5!x555Q506O6-e66$6%667!&7;H76777N7*-8;X888?8!8(9HH99C99H:BJ:6:0::;6%;\;9< <<<*=!2=T= d=Tn=x=H<>>3>*>>? (?%2?X?Rh??!??@@ @Y@AA.@BoBB/BBB C*C(All fieldsFields selectedPredefined exportsCreate...Search...A_ttachments...ActionActions...AddAdd a note to the recordAdd an attachment to the recordAdd existing recordAdd valueAll filesAlways ignore this warning.Are you sure to remove this record?Are you sure to remove those records?Attachments (%s)Attachments...Bookmark Name:Bookmark this filterBy: CSV ParametersC_onnectCancel connection to the Tryton serverClearClose TabCollapse all rowsCollapse rowCompareConcurrency ExceptionConnect the Tryton serverCopy URL into clipboardCopy _URL...Copy selected textCreate a new recordCreate a new record Create new relationCut selected textDatabase:Date FormatDeleteDelete selected record Delimiter:Display formatDisplayed date formatDisplayed valueDo you want to proceed?E-Mail...EditEdit User PreferencesEdit...Encoding:ErrorExpand all rowsExpand rowExpand/CollapseFalseFetching databases listField nameFile to Import:FindFormatFuzzyHeight:Host / Database informationHost:IDID:Image SizeImage size too large.ImagesLaunch actionLimitLimit:Lines to Skip:LinkLoginLogs (%s)MModel:Move CursorMove downMove down of one pageMove to bottomMove to parentMove to topMove upMove up of one pageNameNewNextNext RecordNo action defined.No other language available.No result found.Notes (%s)Notes...OpenOpen filtersOpen related recordsOpen relationOpen reportOpen the calendarOpen the record Open...Open/Search relationOverride '%s' definition?PNG image (*.png)Paste copied textPre-validationPreferencePreferencesPreviousPrevious RecordPrintPrint reportPrint...ProfileProfile EditorProfile:Quote char:Record saved.Records not removed.Records removed.RelateRelate...Remove "%s"Remove Remove selected recordRemove this bookmarkReportReport BugReport...RevisionRevision:SaveSave AsSave As...Save Tree StateSave this recordSearchSearch %sSearch Limit SettingsSearch Limit...Search a record SelectSelect a colorSelect a revisionSelect allSelect parentSelect your actionSelect...Select/Activate current rowSelectionShow bookmarks of filtersShow plain textShow revisions...Spell CheckingSubject:SwitchSwitch viewThe following action requires to close all tabs. Do you want to continue?This record has been modified do you want to save it?To:TodayToggle rowToggle selectionTranslate viewTranslationTrueUnable to set locale %sUnable to set view tree stateUndelete selected record UnknownUnmark line for deletionUnselect allUser name:Username:ValueView _Logs...WeekWhat is the name of this export?Width:WizardWorking now on the duplicated record(s).Write AnywayYYesYou have to select one record.You must select an import file first.You need to save the record before adding translations.Your selection:_Actions..._Add_Auto-Detect_Cancel_Clear_Close Tab_Copy URL_Delete Export_Delete..._Duplicate_E-Mail..._Export Data..._Import Data..._New_Next_Notes..._Previous_Print..._Relate..._Reload/Undo_Remove_Report..._Save_Save Export_Search_Switch Viewddevelopment modego backgo forwardhlogging everything at INFO levelmnext yearprevious yearsspecify alternate config filespecify the log level: DEBUG, INFO, WARNING, ERROR, CRITICALspecify the login usertwyProject-Id-Version: PROJECT VERSION Report-Msgid-Bugs-To: EMAIL@ADDRESS POT-Creation-Date: 2025-04-26 17:11+0200 PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE Last-Translator: FULL NAME Language: lo Language-Team: lo Plural-Forms: nplurals=1; plural=0; MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: 8bit Generated-By: Babel 2.17.0 ແຖວຂໍ້ມູນ %d ຖືກນໍາເຂົ້າແລ້ວ!ແຖວຂໍ້ມູນ %d ຖືກບັນທຶກແລ້ວ!ບັນດາແຖວຂໍ້ມູນ %d ຖືກນໍາເຂົ້າແລ້ວ!ບັນດາແຖວຂໍ້ມູນ %d ຖືກບັນທຶກແລ້ວ!%s (%s)%s (ສະຕຣິງ)%s%%%s/ຊື່ບັນທຶກ,.....:ຊ່ອງຂໍ້ມູນທັງໝົດຊ່ອງຂໍ້ມູນທີ່ເລືອກໄວ້ການສົ່ງອອກທີ່ຈັດຕຽມໄວ້ກ່ອນສ້າງ...ຊອກຫາ...ຄ_ັດຕິດ...ການດຳເນີນການດຳເນີນການ...ເພີ່ມເພີ່ມບັນທຶກຂໍ້ຄວາມໃສ່ແຖວຂໍ້ມູນເພີ່ມເອກະສານຄັດຕິດໃສ່ແຖວຂໍ້ມູນເພີ່ມແຖວຂໍ້ມູນທີ່ມີຢູ່ເພີ່ມຄ່າຟາຍລ໌ ທັງໝົດບໍ່ສົນກັບຄຳເຕືອນນີ້ເລີຍ.ທ່ານແນ່ໃຈບໍ່ທີ່ຈະລຶບແຖວຂໍ້ມູນນີ້?ທ່ານແນ່ໃຈບໍ່ທີ່ຈະລຶບແຖວຂໍ້ມູນເຫຼົ່ານີ້?ເອກະສານຄັດຕິດ (%s)ຄັດຕິດ...ຊື່ໝາຍໜ້າ:ໝາຍໜ້າກັ່ນຕອງນີ້ໂດຍ:ຂອບເຂດການໃຊ້ (parameters) ຂອງ ຟາຍລ໌ CSVເ_ຊື່ອມຕໍ່ຍົກເລີກການເຊື່ອມຕໍ່ກັບແມ່ຂ່າຍ ໄຕຣຕັອນໃຫ້ເປົ່າອັດແຖບງານຍຸບແຖວທັງໝົດຍຸບແຖວເຂົ້າກັນສົມທຽບຍົກເວັ້ນການເຂົ້າເຖິງພ້ອມກັນເຊື່ອມຕໍ່ກັບແມ່ຂ່າຍ ໄຕຣຕັອນກ່າຍເອົາ URL ໃສ່ ຄລິບບອດກ່າຍ_URL...ກ່າຍເອົາຂໍ້ຄວາມທີ່ເລືອກໄວ້ສ້າງແຖວຂໍ້ມູນໃໝ່ສ້າງແຖວຂໍ້ມູນໃໝ່ ສ້າງການກ່ຽວພັນໃໝ່ຕັດຂໍ້ຄວາມທີ່ເລືອກໄວ້ຖານຂໍ້ມູນ:ຮູບແບບວັນທີລືບລຶບແຖວຂໍ້ມູນທີ່ເລືອກ ຕົວຂັ້ນ:ຮູບແບບສະແດງອອກຮູບແບບວັນທີທີ່ສະແດງອອກຄ່າສະແດງອອກທ່ານຕ້ອງການດຳເນີນການຕໍ່ໄປບໍ່?ອີ-ເມວລ໌...ແກ້ໄຂແກ້ໄຂການຕັ້ງຄ່າຜູ້ໃຊ້ງານແກ້ໄຂ...ເຂົ້າລະຫັດ:ຜິດພາດຂະຫຽາຍແຖວທັງໝົດຂະຫຽາຍແຖວອອກຂະຫຽາຍອອກ/ຍຸບເຂົ້າກັນຜິດລາຍການດຶງເອົາຖານຂໍ້ມູນຊື່ຊ່ອງຂໍ້ມູນຟາຍລ໌ທີ່ຈະນໍາເຂົ້າ:ຊອກພົບຮູບແບບເລືອນລາງຄວາມສູງ:ແມ່ຂ່າຍ / ຂໍ້ມູນກ່ຽວກັບຖານຂໍ້ມູນແມ່ຂ່າຍ:ເລກລໍາດັບເລກລໍາດັບ:ຂະໜາດຮູບຂະໜາດຮູບ ໃຫຍ່ໂພດ.ຮູບເປີດການດຳເນີນການຂີດຈຳກັດ:ຂີດຈຳກັດ:ແຖວທີ່ຈະຂວ້າມ:ເຊື່ອມໂຍງເຂົ້າສູ່ລະບົບໄມ້ທ່ອນ (%s)ດແບບ:ຍ້າຍ ເຄິກເຊີຣ໌ຍ້າຍລົງຍ້າຍລົງລຸ່ມໜຶ່ງໜ້າຍ້າຍລົງລຸ່ມຍ້າຍໄປຫາບັນຊີແມ່ຍ້າຍຂຶ້ນເທິງສຸດຍ້າຍຂຶ້ນຫຍັບຂຶ້ນໜຶ່ງໜ້າຊື່ສ້າງໃໝ່ຕໍ່ໄປແຖວຂໍ້ມູນ ຕໍ່ໄປບໍ່ມີ ການດຳເນີນການ ທີ່ລະບຸໄວ້!ບໍ່ມີພາສາອື່ນໃຫ້ໃຊ້.ບໍ່ພົບຜົນຊອກຫາບັນທຶກ (%s)ໝາຍເຫດ...ໄຂໄຂໂຕກັ່ນຕອງໄຂແຖວຂໍ້ມູນທີ່ກ່ຽວພັນໄຂການກ່ຽວພັນໄຂບົດລາຍງານໄຂປະຕິທິນໄຂແຖວຂໍ້ມູນ ໄຂ...ໄຂ/ຊອກຫາການກ່ຽວພັນຂຽນທັບ ຄຳອະທິບາຍ '%s' ນີ້ບໍ່?ຮູບແບບ PNG (*.png)ວາງຂໍ້ຄວາມທີ່ກ່າຍເອົາໃສ່ກ່ອນ-ການກວດສອບການຕັ້ງຄ່າການຕັ້ງຄ່າກ່ອນໜ້າແຖວຂໍ້ມູນກ່ອນນີ້ພິມອອກພິມລາຍງານອອກພິມອອກ...ຂໍ້ມູນລາຍລະອຽດຂໍ້ມູນລາຍລະອຽດບັນນາທິການຂໍ້ມູນລາຍລະອຽດ:ຂໍ້ຄວາມອ້າງອີງ:ແຖວຂໍ້ມູນບັນທຶກແລ້ວແຖວຂໍ້ມູນບໍ່ທັນຖືກເອົາອອກ.ແຖວຂໍ້ມູນຖືກເອົາອອກແລ້ວ.ທີ່ກ່ຽວພັນທີ່ກ່ຽວພັນ...ເອົາ "%s" ອອກເອົາອອກເອົາແຖວຂໍ້ມູນທີ່ເລືອກນີ້ອອກເອົາໝາຍໜ້ານີ້ອອກລາຍງານ...ລາຍງານຂໍ້ຜິດພາດລາຍງານ...ການກວດແກ້ຄືນການກວດແກ້ຄືນ:ບັນທຶກບັນທຶກເປັນບັນທຶກເປັນ...ບັນທຶກ ສະຖານະ ໂຄງສ້າງບັນທຶກແຖວຂໍ້ມູນນີ້ຊອກຫາຊອກຫາ %sການຕັ້ງຄ່າຂີດຈຳກັດການຊອກຫາຈຳກັດການຊອກຫາ...ຊອກຫາແຖວຂໍ້ມູນໜຶ່ງ ເລືອກ...ເລືອກສີເລືອກໜຶ່ງການກວດແກ້ຄືນເລືອກທັງໝົດເລືອກ ບັນຊີແມ່ເລືອກການດຳເນີນການຂອງທ່ານເລືອກ...ເລືອກ/ໃຊ້ງານແຖວປັດຈຸບັນການເລືອກສະແດງໝາຍໜ້າຂອງການກັ່ນຕອງສະແດງເປັນຂໍ້ຄວາມທຳມະດາສະແດງການກວດແກ້ຄືນ...ກວດສອບການສະກົດຄຳຫົວເລື່ອງ:ສະຫຼັບສະຫຼັບປ່ຽນການເບິ່ງການດຳເນີນການດັ່ງຕໍ່ໄປນີ້ ຕ້ອງການໃຫ້ ອັດແຖບງານທັງໝົດລົງ ທ່ານຕ້ອງການສືບຕໍ່ບໍ່?ແຖວຂໍ້ມູນນີ້ ຖືກແກ້ໄຂແລ້ວ ທ່ານຕ້ອງການບັນທຶກໄວ້ບໍ່?ເຖິງ:ມື້ນີ້ສະຫຼັບແຖວສະຫຼັບການເລືອກມູມມອງການແປການແປຈິງບໍ່ສາມາດຕັ້ງຄ່າ ທ້ອງຖິ່ນ %s ໄດ້.ບໍ່ສາມາດຕັ້ງຄ່າສະຖານະມູມມອງແບບຕົ້ນໄມ້ໄດ້ຍົກເລີກການລຶບແຖວທີ່ເລືອກບໍ່ຮູ້ບໍ່ໝາຍແຖວທີ່ຈະລຶບບໍ່ເລືອກທັງໝົດຊື່ຜູ້ໃຊ້:ຜູ້ໃຊ້:ຄ່າເບິ່ງ_ບັນທຶກ...ອາທິດຊື່ຂອງການສົ່ງອອກນີ້ແມ່ນຫຍັງ?ຄວາມກວ້າງ:ໂຕນໍາພາສ້າງດຽວນີ້ກຳລັງເຮັດວຽກກັບແຖວຂໍ້ມູນທີ່ສ້າງຂຶ້ນຊ້ອນກັນ.ຂຽນລົງເລີຍປແມ່ນທ່ານຕ້ອງໄດ້ເລືອກ ໜຶ່ງແຖວຂໍ້ມູນ.ກ່ອນອື່ນ ທ່ານຕ້ອງໄດ້ເລືອກ ຟາຍລ໌ ທີ່ຈະນໍາເຂົ້າກ່ອນ.ທ່ານຕ້ອງບັນທຶກຂໍ້ມູນໄວ້ກ່ອນຈຶ່ງເພີ່ມການແປເຂົ້າໄປໄດ້.ການເລືອກຂອງທ່ານ:_ດຳເນີນການ..._ເພີ່ມເຂົ້າ_ກວດຫາ-ອັດຕະໂນມັດ_ຍົກເລີກ_ໃຫ້ເປົາ_ອັດແຖບງານ_ກ່າຍ URL_ລຶບການສົ່ງອອກ_ລຶບ..._ເຮັດຊໍ້າກັນ_ອີ-ແມວລ໌.._ສົ່ງອອກຂໍ້ມູນ..._ນໍາເຂົ້າຂໍ້ມູນ..._ສ້າງໃໝ່_ຕໍ່ໄປ_ຈົດບັນທຶກ..._ກ່ອນໜ້າ_ພິມອອກ..._ທີ່ກ່ຽວພັນ..._ໂຫຼດຄືນໃໝ່/ຍົກເລີກ_ເອົາອອກ_ລາຍງານ..._ບັນທຶກ_ບັນທຶກການສົ່ງອອກ_ຊອກຫາ_ສະຫຼັບໜ້າເບິ່ງມຮູບແບບການພັດທະນາກັບຄືນໄປໜ້າມ.ເຂົ້າໃຊ້ທຸກຢ່າງໃນລະດັບຂໍ້ມູນນປີຕໍ່ໄປປີກ່ອນວລະບຸ config file ທີ່ສະຫຼັບກັນລະບຸ ລະດັບ ການບັນທຶກ: ແກ້ໄຂຂໍ້ຜິດພາດ, ຂໍ້ມູນ, ຄຳເຕືອນ, ຂໍ້ຜິດພາດ, ທີ່ສຳຄັນລະບຸ ຜູ້ເຂົ້າໃຊ້ງານຈອມ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1703673879.0 tryton-7.0.24/tryton/data/locale/lo/LC_MESSAGES/tryton.po0000644000175000017500000006520014543000027021056 0ustar00cedced# Translations template for tryton. # Copyright (C) 2016 Tryton # This file is distributed under the same license as the tryton project. # FIRST AUTHOR , 2016. msgid "" msgstr "Content-Type: text/plain; charset=utf-8\n" msgid "specify alternate config file" msgstr "ລະບຸ config file ທີ່ສະຫຼັບກັນ" msgid "development mode" msgstr "ຮູບແບບການພັດທະນາ" msgid "logging everything at INFO level" msgstr "ເຂົ້າໃຊ້ທຸກຢ່າງໃນລະດັບຂໍ້ມູນ" msgid "specify the log level: DEBUG, INFO, WARNING, ERROR, CRITICAL" msgstr "" "ລະບຸ ລະດັບ ການບັນທຶກ: ແກ້ໄຂຂໍ້ຜິດພາດ, ຂໍ້ມູນ, ຄຳເຕືອນ, ຂໍ້ຜິດພາດ, ທີ່ສຳຄັນ" msgid "specify the file used to output logging information" msgstr "" msgid "specify the login user" msgstr "ລະບຸ ຜູ້ເຂົ້າໃຊ້ງານ" #, fuzzy msgid "specify the server hostname:port" msgstr "ລະບຸ ທີ່ຢູ່ ຂອງ ເຄື່ອງແມ່ຂ່າຍ" msgid "disable thread usage" msgstr "" #, python-format msgid "Unable to set locale %s" msgstr "ບໍ່ສາມາດຕັ້ງຄ່າ ທ້ອງຖິ່ນ %s ໄດ້." msgid ", " msgstr "," msgid ",..." msgstr "" #, python-format msgid "%s (%s)" msgstr "%s (%s)" msgid "Select your action" msgstr "ເລືອກການດຳເນີນການຂອງທ່ານ" msgid "No action defined." msgstr "ບໍ່ມີ ການດຳເນີນການ ທີ່ລະບຸໄວ້!" msgid "By: " msgstr "ໂດຍ:" msgid "Selection" msgstr "ການເລືອກ" #, fuzzy msgid "Cancel" msgstr "_ຍົກເລີກ" msgid "OK" msgstr "" msgid "Your selection:" msgstr "ການເລືອກຂອງທ່ານ:" msgid "Save As..." msgstr "ບັນທຶກເປັນ..." msgid "Do you want to proceed?" msgstr "ທ່ານຕ້ອງການດຳເນີນການຕໍ່ໄປບໍ່?" msgid "Always ignore this warning." msgstr "ບໍ່ສົນກັບຄຳເຕືອນນີ້ເລີຍ." msgid "No" msgstr "" msgid "Yes" msgstr "ແມ່ນ" msgid "Concurrency Exception" msgstr "ຍົກເວັ້ນການເຂົ້າເຖິງພ້ອມກັນ" msgid "This record has been modified while you were editing it." msgstr "" msgid "Cancel saving" msgstr "" msgid "Compare" msgstr "ສົມທຽບ" msgid "See the modified version" msgstr "" msgid "Write Anyway" msgstr "ຂຽນລົງເລີຍ" msgid "Save your current version" msgstr "" #, fuzzy, python-format msgid "Compare: %s" msgstr "ສົມທຽບ: %s" msgid "Close" msgstr "" #, fuzzy msgid "Application Error" msgstr "ໂປຣແກຣມ ມີຂໍ້ຜິດພາດ!" msgid "Report Bug" msgstr "ລາຍງານຂໍ້ຜິດພາດ" #, python-format msgid "Check URL: %s" msgstr "" msgid "Unable to check for new version." msgstr "" msgid "A new version is available!" msgstr "" msgid "Download" msgstr "" #, fuzzy msgid "Could not get a session." msgstr "ບໍ່ສາມາດເຊື່ອມຕໍ່ກັບແມ່ຂ່າຍໄດ້" msgid "Too many requests. Try again later." msgstr "" #, fuzzy msgid "Not found." msgstr "ບໍ່ພົບຜົນຊອກຫາ" #, fuzzy msgid "Not Found." msgstr "ບໍ່ພົບຜົນຊອກຫາ" msgid "..." msgstr "..." msgid "Search..." msgstr "ຊອກຫາ..." msgid "Create..." msgstr "ສ້າງ..." #, fuzzy, python-format msgid "Create \"%s\"..." msgstr "ສ້າງ \"%s\"..." msgid "Value" msgstr "ຄ່າ" msgid "Displayed value" msgstr "ຄ່າສະແດງອອກ" msgid "Format" msgstr "ຮູບແບບ" msgid "Display format" msgstr "ຮູບແບບສະແດງອອກ" msgid "Open the calendar" msgstr "ໄຂປະຕິທິນ" msgid "Date Format" msgstr "ຮູບແບບວັນທີ" msgid "Displayed date format" msgstr "ຮູບແບບວັນທີທີ່ສະແດງອອກ" msgid "y" msgstr "ມ" msgid "True" msgstr "ຈິງ" msgid "t" msgstr "ຈ" msgid "False" msgstr "ຜິດ" msgid "Digits" msgstr "" msgid "The number of decimal" msgstr "" #, fuzzy msgid "Template" msgstr "ທີ່ກ່ຽວພັນ" msgid "Edit..." msgstr "ແກ້ໄຂ..." #, fuzzy msgid "View Logs..." msgstr "ເບິ່ງ_ບັນທຶກ..." msgid "Attachments..." msgstr "ຄັດຕິດ..." msgid "Notes..." msgstr "ໝາຍເຫດ..." msgid "Actions..." msgstr "ດຳເນີນການ..." msgid "Relate..." msgstr "ທີ່ກ່ຽວພັນ..." msgid "Report..." msgstr "ລາຍງານ..." msgid "Print..." msgstr "ພິມອອກ..." msgid "E-Mail..." msgstr "ອີ-ເມວລ໌..." msgid "Bold" msgstr "" msgid "Italic" msgstr "" msgid "Underline" msgstr "" msgid "Align Left" msgstr "" msgid "Align Center" msgstr "" msgid "Align Right" msgstr "" msgid "Justify" msgstr "" msgid "Foreground Color" msgstr "" msgid "Select a color" msgstr "ເລືອກສີ" msgid "Y" msgstr "ປ" msgid "M" msgstr "ດ" msgid "w" msgstr "ອ" msgid "d" msgstr "ມ" msgid "h" msgstr "ມ." msgid "m" msgstr "ນ" msgid "s" msgstr "ວ" #, fuzzy msgid "Preferences..." msgstr "_ການຕັ້ງຄ່າຂອງ..." #, fuzzy msgid "Toolbar" msgstr "_ແຖບເຄື່ອງມື" #, fuzzy msgid "Default" msgstr "_ຄ່າເດີມ" #, fuzzy msgid "Text and Icons" msgstr "_ຂໍ້ຄວາມ ແລະ ປຸ່ມລັດ" #, fuzzy msgid "Text" msgstr "_ຂໍ້ຄວາມ" #, fuzzy msgid "Icons" msgstr "_ປຸ່ມລັດ" #, fuzzy msgid "Form" msgstr "_ແບບຟອມ" msgid "Save Column Width" msgstr "" msgid "Save Tree State" msgstr "ບັນທຶກ ສະຖານະ ໂຄງສ້າງ" msgid "Spell Checking" msgstr "ກວດສອບການສະກົດຄຳ" msgid "Play Sound for Code Scanner" msgstr "" msgid "PDA Mode" msgstr "" msgid "Search Limit..." msgstr "ຈຳກັດການຊອກຫາ..." msgid "Check Version" msgstr "" #, fuzzy msgid "Options" msgstr "_ທາງເລືອກ" #, fuzzy msgid "Documentation..." msgstr "ດຳເນີນການ..." #, fuzzy msgid "Keyboard Shortcuts..." msgstr "_ປຸ່ມລັດທາງແປ້ນພິມ..." #, fuzzy msgid "About..." msgstr "_ກ່ຽວກັບ..." #, fuzzy msgid "Help" msgstr "_ຊ່ອຍເຫຼືອ" msgid "No result found." msgstr "ບໍ່ພົບຜົນຊອກຫາ" #, fuzzy msgid "Favorites" msgstr "ທີ່_ມັກຫຼາຍ" msgid "Manage..." msgstr "" msgid "Action" msgstr "ການດຳເນີນການ" msgid "" "The following action requires to close all tabs.\n" "Do you want to continue?" msgstr "" "ການດຳເນີນການດັ່ງຕໍ່ໄປນີ້ ຕ້ອງການໃຫ້ ອັດແຖບງານທັງໝົດລົງ\n" "ທ່ານຕ້ອງການສືບຕໍ່ບໍ່?" msgid "Application Shortcuts" msgstr "" msgid "Global" msgstr "" msgid "Preferences" msgstr "ການຕັ້ງຄ່າ" msgid "Search menu" msgstr "" msgid "Toggle menu" msgstr "" #, fuzzy msgid "Previous tab" msgstr "_ແຖບງານກ່ອນໜ້າ" #, fuzzy msgid "Next tab" msgstr "_ແຖບງານ ຕໍ່ໄປ" msgid "Shortcuts" msgstr "" msgid "Quit" msgstr "" msgid "Edition Shortcuts" msgstr "" msgid "Text Entries" msgstr "" msgid "Cut selected text" msgstr "ຕັດຂໍ້ຄວາມທີ່ເລືອກໄວ້" msgid "Copy selected text" msgstr "ກ່າຍເອົາຂໍ້ຄວາມທີ່ເລືອກໄວ້" msgid "Paste copied text" msgstr "ວາງຂໍ້ຄວາມທີ່ກ່າຍເອົາໃສ່" msgid "Next entry" msgstr "" msgid "Previous entry" msgstr "" msgid "Relation Entries" msgstr "" msgid "Create new relation" msgstr "ສ້າງການກ່ຽວພັນໃໝ່" msgid "Open/Search relation" msgstr "ໄຂ/ຊອກຫາການກ່ຽວພັນ" msgid "List Entries" msgstr "" msgid "Switch view" msgstr "ສະຫຼັບປ່ຽນການເບິ່ງ" msgid "Create/Select new line" msgstr "" msgid "Open relation" msgstr "ໄຂການກ່ຽວພັນ" msgid "Mark line for deletion/removal" msgstr "" msgid "Mark line for removal" msgstr "" msgid "Unmark line for deletion" msgstr "ບໍ່ໝາຍແຖວທີ່ຈະລຶບ" msgid "List/Tree Shortcuts" msgstr "" msgid "Move Cursor" msgstr "ຍ້າຍ ເຄິກເຊີຣ໌" #, fuzzy msgid "Move right" msgstr "ຍ້າຍໄປຂວາ" #, fuzzy msgid "Move left" msgstr "ຍ້າຍໄປຊ້າຍ" msgid "Move up" msgstr "ຍ້າຍຂຶ້ນ" msgid "Move down" msgstr "ຍ້າຍລົງ" msgid "Move up of one page" msgstr "ຫຍັບຂຶ້ນໜຶ່ງໜ້າ" msgid "Move down of one page" msgstr "ຍ້າຍລົງລຸ່ມໜຶ່ງໜ້າ" msgid "Move to top" msgstr "ຍ້າຍຂຶ້ນເທິງສຸດ" msgid "Move to bottom" msgstr "ຍ້າຍລົງລຸ່ມ" msgid "Move to parent" msgstr "ຍ້າຍໄປຫາບັນຊີແມ່" #, fuzzy msgid "Edition" msgstr "ແກ້ໄຂ" #, fuzzy msgid "Copy selected rows" msgstr "ກ່າຍເອົາຂໍ້ຄວາມທີ່ເລືອກໄວ້" msgid "Insert copied rows" msgstr "" msgid "Select all" msgstr "ເລືອກທັງໝົດ" msgid "Unselect all" msgstr "ບໍ່ເລືອກທັງໝົດ" msgid "Select parent" msgstr "ເລືອກ ບັນຊີແມ່" msgid "Select/Activate current row" msgstr "ເລືອກ/ໃຊ້ງານແຖວປັດຈຸບັນ" msgid "Toggle selection" msgstr "ສະຫຼັບການເລືອກ" msgid "Expand/Collapse" msgstr "ຂະຫຽາຍອອກ/ຍຸບເຂົ້າກັນ" msgid "Expand row" msgstr "ຂະຫຽາຍແຖວອອກ" msgid "Collapse row" msgstr "ຍຸບແຖວເຂົ້າກັນ" msgid "Toggle row" msgstr "ສະຫຼັບແຖວ" msgid "Collapse all rows" msgstr "ຍຸບແຖວທັງໝົດ" msgid "Expand all rows" msgstr "ຂະຫຽາຍແຖວທັງໝົດ" msgid "Close Tab" msgstr "ອັດແຖບງານ" msgid "modularity, scalability and security" msgstr "" msgid "translator-credits" msgstr "" #, python-format msgid "Attachments (%s)" msgstr "ເອກະສານຄັດຕິດ (%s)" msgid "Code Scanner" msgstr "" msgid "Code" msgstr "" msgid "Profile Editor" msgstr "ຂໍ້ມູນລາຍລະອຽດບັນນາທິການ" msgid "Profile" msgstr "ຂໍ້ມູນລາຍລະອຽດ" msgid "Add new profile" msgstr "" msgid "Remove selected profile" msgstr "" msgid "Host:" msgstr "ແມ່ຂ່າຍ:" msgid "Database:" msgstr "ຖານຂໍ້ມູນ:" msgid "Fetching databases list" msgstr "ລາຍການດຶງເອົາຖານຂໍ້ມູນ" msgid "Username:" msgstr "ຜູ້ໃຊ້:" #, fuzzy msgid "Incompatible version of the server." msgstr "ບໍ່ຖືກລຸ່ນກັບແມ່ຂ່າຍ" #, fuzzy msgid "Could not connect to the server." msgstr "ບໍ່ສາມາດເຊື່ອມຕໍ່ກັບແມ່ຂ່າຍໄດ້" msgid "Login" msgstr "ເຂົ້າສູ່ລະບົບ" msgid "_Cancel" msgstr "_ຍົກເລີກ" msgid "Cancel connection to the Tryton server" msgstr "ຍົກເລີກການເຊື່ອມຕໍ່ກັບແມ່ຂ່າຍ ໄຕຣຕັອນ" msgid "C_onnect" msgstr "ເ_ຊື່ອມຕໍ່" msgid "Connect the Tryton server" msgstr "ເຊື່ອມຕໍ່ກັບແມ່ຂ່າຍ ໄຕຣຕັອນ" msgid "Profile:" msgstr "ຂໍ້ມູນລາຍລະອຽດ:" msgid "Host / Database information" msgstr "ແມ່ຂ່າຍ / ຂໍ້ມູນກ່ຽວກັບຖານຂໍ້ມູນ" msgid "User name:" msgstr "ຊື່ຜູ້ໃຊ້:" #, fuzzy msgid "Unable to complete email entry" msgstr "ບໍ່ສາມາດຕັ້ງຄ່າ ທ້ອງຖິ່ນ %s ໄດ້." #, fuzzy, python-format msgid "E-mail %s" msgstr "ອີເມວລ໌ %s" msgid "To:" msgstr "ເຖິງ:" #, fuzzy msgid "Cc:" msgstr "CC:" msgid "Bcc:" msgstr "" msgid "Subject:" msgstr "ຫົວເລື່ອງ:" #, fuzzy msgid "Body" msgstr "ເນື້ອຄວາມ:" #, fuzzy msgid "Reports" msgstr "ລາຍງານ..." #, fuzzy msgid "Attachments" msgstr "ເອກະສານຄັດຕິດ:" msgid "Files" msgstr "" msgid "Send" msgstr "" #, fuzzy msgid "Select File" msgstr "ເລືອກທັງໝົດ" #, fuzzy msgid "Remove File" msgstr "ເອົາອອກ" msgid "Select" msgstr "ເລືອກ..." msgid "Add..." msgstr "" #, fuzzy msgid "Preview" msgstr "ກ່ອນໜ້າ" msgid "Previous" msgstr "ກ່ອນໜ້າ" msgid "Next" msgstr "ຕໍ່ໄປ" #, fuzzy, python-format msgid "Attachment (%s)" msgstr "ເອກະສານຄັດຕິດ (%s)" #, python-format msgid "Note (%d/%d)" msgstr "" msgid "You have to select one record." msgstr "ທ່ານຕ້ອງໄດ້ເລືອກ ໜຶ່ງແຖວຂໍ້ມູນ." msgid "Are you sure to remove this record?" msgstr "ທ່ານແນ່ໃຈບໍ່ທີ່ຈະລຶບແຖວຂໍ້ມູນນີ້?" msgid "Are you sure to remove those records?" msgstr "ທ່ານແນ່ໃຈບໍ່ທີ່ຈະລຶບແຖວຂໍ້ມູນເຫຼົ່ານີ້?" msgid "Records not removed." msgstr "ແຖວຂໍ້ມູນບໍ່ທັນຖືກເອົາອອກ." msgid "Records removed." msgstr "ແຖວຂໍ້ມູນຖືກເອົາອອກແລ້ວ." msgid "Working now on the duplicated record(s)." msgstr "ດຽວນີ້ກຳລັງເຮັດວຽກກັບແຖວຂໍ້ມູນທີ່ສ້າງຂຶ້ນຊ້ອນກັນ." msgid "Record saved." msgstr "ແຖວຂໍ້ມູນບັນທຶກແລ້ວ" msgid "" "This record has been modified\n" "do you want to save it?" msgstr "ແຖວຂໍ້ມູນນີ້ ຖືກແກ້ໄຂແລ້ວ ທ່ານຕ້ອງການບັນທຶກໄວ້ບໍ່?" msgid "Launch action" msgstr "ເປີດການດຳເນີນການ" msgid "Relate" msgstr "ທີ່ກ່ຽວພັນ" msgid "Open related records" msgstr "ໄຂແຖວຂໍ້ມູນທີ່ກ່ຽວພັນ" msgid "Report" msgstr "ລາຍງານ..." msgid "Open report" msgstr "ໄຂບົດລາຍງານ" msgid "Print" msgstr "ພິມອອກ" msgid "Print report" msgstr "ພິມລາຍງານອອກ" msgid "_Copy URL" msgstr "_ກ່າຍ URL" msgid "Copy URL into clipboard" msgstr "ກ່າຍເອົາ URL ໃສ່ ຄລິບບອດ" msgid "Unknown" msgstr "ບໍ່ຮູ້" msgid "Limit" msgstr "ຂີດຈຳກັດ:" msgid "Search Limit Settings" msgstr "ການຕັ້ງຄ່າຂີດຈຳກັດການຊອກຫາ" msgid "Limit:" msgstr "ຂີດຈຳກັດ:" #, python-format msgid "Logs (%s)" msgstr "ໄມ້ທ່ອນ (%s)" msgid "Model:" msgstr "ແບບ:" msgid "ID:" msgstr "ເລກລໍາດັບ:" #, fuzzy msgid "Created by:" msgstr "ວັນທີສ້າງ" #, fuzzy msgid "Created at:" msgstr "ວັນທີສ້າງ" #, fuzzy msgid "Last Modified by:" msgstr "ປັບປ່ຽນລ້າສຸດ ໂດຍ:" #, fuzzy msgid "Last Modified at:" msgstr "ວັນທີປັບປ່ຽນລ້າສຸດ:" #, python-format msgid "Notes (%s)" msgstr "ບັນທຶກ (%s)" msgid "Edit User Preferences" msgstr "ແກ້ໄຂການຕັ້ງຄ່າຜູ້ໃຊ້ງານ" msgid "Preference" msgstr "ການຕັ້ງຄ່າ" msgid "Revision" msgstr "ການກວດແກ້ຄືນ" msgid "Select a revision" msgstr "ເລືອກໜຶ່ງການກວດແກ້ຄືນ" msgid "Revision:" msgstr "ການກວດແກ້ຄືນ:" msgid "_Switch View" msgstr "_ສະຫຼັບໜ້າເບິ່ງ" #, fuzzy msgid "Switch View" msgstr "_ສະຫຼັບໜ້າເບິ່ງ" msgid "_Previous" msgstr "_ກ່ອນໜ້າ" msgid "Previous Record" msgstr "ແຖວຂໍ້ມູນກ່ອນນີ້" msgid "_Next" msgstr "_ຕໍ່ໄປ" msgid "Next Record" msgstr "ແຖວຂໍ້ມູນ ຕໍ່ໄປ" msgid "_Search" msgstr "_ຊອກຫາ" msgid "_New" msgstr "_ສ້າງໃໝ່" msgid "Create a new record" msgstr "ສ້າງແຖວຂໍ້ມູນໃໝ່" msgid "_Save" msgstr "_ບັນທຶກ" msgid "Save this record" msgstr "ບັນທຶກແຖວຂໍ້ມູນນີ້" msgid "_Reload/Undo" msgstr "_ໂຫຼດຄືນໃໝ່/ຍົກເລີກ" #, fuzzy msgid "Reload/Undo" msgstr "_ໂຫຼດຄືນໃໝ່/ຍົກເລີກ" msgid "_Duplicate" msgstr "_ເຮັດຊໍ້າກັນ" msgid "_Delete..." msgstr "_ລຶບ..." msgid "View _Logs..." msgstr "ເບິ່ງ_ບັນທຶກ..." msgid "Show revisions..." msgstr "ສະແດງການກວດແກ້ຄືນ..." msgid "A_ttachments..." msgstr "ຄ_ັດຕິດ..." msgid "Add an attachment to the record" msgstr "ເພີ່ມເອກະສານຄັດຕິດໃສ່ແຖວຂໍ້ມູນ" msgid "_Notes..." msgstr "_ຈົດບັນທຶກ..." msgid "Add a note to the record" msgstr "ເພີ່ມບັນທຶກຂໍ້ຄວາມໃສ່ແຖວຂໍ້ມູນ" msgid "_Actions..." msgstr "_ດຳເນີນການ..." msgid "_Relate..." msgstr "_ທີ່ກ່ຽວພັນ..." msgid "_Report..." msgstr "_ລາຍງານ..." msgid "_Print..." msgstr "_ພິມອອກ..." msgid "_E-Mail..." msgstr "_ອີ-ແມວລ໌.." #, fuzzy msgid "Send an e-mail using the record" msgstr "ເພີ່ມບັນທຶກຂໍ້ຄວາມໃສ່ແຖວຂໍ້ມູນ" msgid "_Export Data..." msgstr "_ສົ່ງອອກຂໍ້ມູນ..." msgid "_Import Data..." msgstr "_ນໍາເຂົ້າຂໍ້ມູນ..." msgid "Copy _URL..." msgstr "ກ່າຍ_URL..." msgid "_Close Tab" msgstr "_ອັດແຖບງານ" msgid "All fields" msgstr "ຊ່ອງຂໍ້ມູນທັງໝົດ" msgid "_Add" msgstr "_ເພີ່ມເຂົ້າ" msgid "_Remove" msgstr "_ເອົາອອກ" msgid "_Clear" msgstr "_ໃຫ້ເປົາ" msgid "Fields selected" msgstr "ຊ່ອງຂໍ້ມູນທີ່ເລືອກໄວ້" msgid "CSV Parameters" msgstr "ຂອບເຂດການໃຊ້ (parameters) ຂອງ ຟາຍລ໌ CSV" msgid "Delimiter:" msgstr "ຕົວຂັ້ນ:" msgid "Quote char:" msgstr "ຂໍ້ຄວາມອ້າງອີງ:" msgid "Encoding:" msgstr "ເຂົ້າລະຫັດ:" msgid "Use locale format" msgstr "" msgid "Field name" msgstr "ຊື່ຊ່ອງຂໍ້ມູນ" #, python-format msgid "CSV Export: %s" msgstr "" msgid "_Save Export" msgstr "_ບັນທຶກການສົ່ງອອກ" #, fuzzy msgid "_URL Export" msgstr "_ບັນທຶກການສົ່ງອອກ" msgid "_Delete Export" msgstr "_ລຶບການສົ່ງອອກ" msgid "Predefined exports" msgstr "ການສົ່ງອອກທີ່ຈັດຕຽມໄວ້ກ່ອນ" msgid "Name" msgstr "ຊື່" msgid "Open" msgstr "ໄຂ" msgid "Save" msgstr "ບັນທຶກ" #, fuzzy msgid "Listed Records" msgstr "ແກ້ໄຂ ແຖວຂໍ້ມູນ ທີ່ເລືອກ " #, fuzzy msgid "Selected Records" msgstr "ແກ້ໄຂ ແຖວຂໍ້ມູນ ທີ່ເລືອກ " msgid "Ignore search limit" msgstr "" #, fuzzy msgid "Add field names" msgstr "ເພີ່ມ_ຊ່ອງຂໍ້ມູນ" #, python-format msgid "%s (string)" msgstr "%s (ສະຕຣິງ)" #, python-format msgid "%s (model name)" msgstr "" #, python-format msgid "%s/Record Name" msgstr "%s/ຊື່ບັນທຶກ" msgid "What is the name of this export?" msgstr "ຊື່ຂອງການສົ່ງອອກນີ້ແມ່ນຫຍັງ?" #, python-format msgid "Override '%s' definition?" msgstr "ຂຽນທັບ ຄຳອະທິບາຍ '%s' ນີ້ບໍ່?" #, python-format msgid "%d record saved." msgstr "ແຖວຂໍ້ມູນ %d ຖືກບັນທຶກແລ້ວ!" #, python-format msgid "%d records saved." msgstr "ບັນດາແຖວຂໍ້ມູນ %d ຖືກບັນທຶກແລ້ວ!" msgid "Export failed" msgstr "" msgid "Link" msgstr "ເຊື່ອມໂຍງ" msgid "Delete" msgstr "ລືບ" msgid "Discard changes" msgstr "" msgid "Save and New" msgstr "" #, fuzzy msgid "Add and New" msgstr "ເພີ່ມຄ່າ" msgid "Add" msgstr "ເພີ່ມ" msgid "Apply changes" msgstr "" msgid "Switch" msgstr "ສະຫຼັບ" msgid "Remove " msgstr "ເອົາອອກ" msgid "Create a new record " msgstr "ສ້າງແຖວຂໍ້ມູນໃໝ່ " msgid "Delete selected record " msgstr "ລຶບແຖວຂໍ້ມູນທີ່ເລືອກ " msgid "Undelete selected record " msgstr "ຍົກເລີກການລຶບແຖວທີ່ເລືອກ" #, python-format msgid "CSV Import: %s" msgstr "" msgid "_Auto-Detect" msgstr "_ກວດຫາ-ອັດຕະໂນມັດ" msgid "File to Import:" msgstr "ຟາຍລ໌ທີ່ຈະນໍາເຂົ້າ:" msgid "Open..." msgstr "ໄຂ..." msgid "Lines to Skip:" msgstr "ແຖວທີ່ຈະຂວ້າມ:" msgid "You must select an import file first." msgstr "ກ່ອນອື່ນ ທ່ານຕ້ອງໄດ້ເລືອກ ຟາຍລ໌ ທີ່ຈະນໍາເຂົ້າກ່ອນ." msgid "Detection failed" msgstr "" #, python-format msgid "Unknown column header \"%s\"" msgstr "" msgid "Error" msgstr "ຜິດພາດ" msgid "Import failed" msgstr "" #, python-format msgid "%d record imported." msgstr "ແຖວຂໍ້ມູນ %d ຖືກນໍາເຂົ້າແລ້ວ!" #, python-format msgid "%d records imported." msgstr "ບັນດາແຖວຂໍ້ມູນ %d ຖືກນໍາເຂົ້າແລ້ວ!" msgid "Search" msgstr "ຊອກຫາ" msgid "New" msgstr "ສ້າງໃໝ່" #, python-format msgid "Search %s" msgstr "ຊອກຫາ %s" msgid "Wizard" msgstr "ໂຕນໍາພາສ້າງ" msgid "ID" msgstr "ເລກລໍາດັບ" #, fuzzy msgid "Created by" msgstr "ວັນທີສ້າງ" #, fuzzy msgid "Created at" msgstr "ວັນທີສ້າງ" #, fuzzy msgid "Edited by" msgstr "ແກ້ໄຂ" #, fuzzy msgid "Edited at" msgstr "ແກ້ໄຂ" msgid "Unable to set view tree state" msgstr "ບໍ່ສາມາດຕັ້ງຄ່າສະຖານະມູມມອງແບບຕົ້ນໄມ້ໄດ້" #, fuzzy, python-format msgid "\"%s\" is not valid according to its domain." msgstr "\"%s\" ບໍ່ຖືກຕ້ອງ ກັບ ໂດເມນຂອງມັນ" #, fuzzy, python-format msgid "\"%s\" is required." msgstr "ຕ້ອງໃຫ້ມີ \"%s\"" #, fuzzy, python-format msgid "The values of \"%s\" are not valid." msgstr "ຄ່າຂອງ \"%s\" ນີ້ ບໍ່ຖືກຕ້ອງ" msgid "Pre-validation" msgstr "ກ່ອນ-ການກວດສອບ" msgid ":" msgstr ":" msgid "Scan" msgstr "" msgid "Image Size" msgstr "ຂະໜາດຮູບ" msgid "Width:" msgstr "ຄວາມກວ້າງ:" msgid "Height:" msgstr "ຄວາມສູງ:" msgid "PNG image (*.png)" msgstr "ຮູບແບບ PNG (*.png)" msgid "Save As" msgstr "ບັນທຶກເປັນ" msgid "Image size too large." msgstr "ຂະໜາດຮູບ ໃຫຍ່ໂພດ." msgid "Copy" msgstr "" msgid "Paste" msgstr "" msgid ".." msgstr ".." msgid "Open filters" msgstr "ໄຂໂຕກັ່ນຕອງ" msgid "Show bookmarks of filters" msgstr "ສະແດງໝາຍໜ້າຂອງການກັ່ນຕອງ" msgid "Remove this bookmark" msgstr "ເອົາໝາຍໜ້ານີ້ອອກ" msgid "Bookmark this filter" msgstr "ໝາຍໜ້າກັ່ນຕອງນີ້" msgid "Show active records" msgstr "" msgid "Show inactive records" msgstr "" msgid "Bookmark Name:" msgstr "ຊື່ໝາຍໜ້າ:" msgid "Find" msgstr "ຊອກພົບ" msgid "Today" msgstr "ມື້ນີ້" msgid "go back" msgstr "ກັບຄືນ" msgid "go forward" msgstr "ໄປໜ້າ" msgid "previous year" msgstr "ປີກ່ອນ" msgid "next year" msgstr "ປີຕໍ່ໄປ" msgid "Day" msgstr "" msgid "Week" msgstr "ອາທິດ" #, fuzzy msgid "Month" msgstr "ເບິ່ງເປັນເດືອນ" msgid "Select..." msgstr "ເລືອກ..." msgid "Clear" msgstr "ໃຫ້ເປົ່າ" msgid "All files" msgstr "ຟາຍລ໌ ທັງໝົດ" msgid "Show plain text" msgstr "ສະແດງເປັນຂໍ້ຄວາມທຳມະດາ" msgid "Add value" msgstr "ເພີ່ມຄ່າ" #, python-format msgid "Remove \"%s\"" msgstr "ເອົາ \"%s\" ອອກ" msgid "Images" msgstr "ຮູບ" msgid "Add existing record" msgstr "ເພີ່ມແຖວຂໍ້ມູນທີ່ມີຢູ່" msgid "Remove selected record" msgstr "ເອົາແຖວຂໍ້ມູນທີ່ເລືອກນີ້ອອກ" msgid "Open the record " msgstr "ໄຂແຖວຂໍ້ມູນ " #, fuzzy msgid "Clear the field " msgstr "ອະນາໄມແຖວຂໍ້ມູນ " msgid "Search a record " msgstr "ຊອກຫາແຖວຂໍ້ມູນໜຶ່ງ " #, fuzzy msgid "Edit selected record" msgstr "ແກ້ໄຂ ແຖວຂໍ້ມູນ ທີ່ເລືອກ " #, fuzzy msgid "Delete selected record" msgstr "ເອົາແຖວຂໍ້ມູນທີ່ເລືອກນີ້ອອກ" #, python-format msgid "%s%%" msgstr "%s%%" msgid "Choose a language" msgstr "" msgid "Translation" msgstr "ການແປ" msgid "Edit" msgstr "ແກ້ໄຂ" msgid "Fuzzy" msgstr "ເລືອນລາງ" msgid "You need to save the record before adding translations." msgstr "ທ່ານຕ້ອງບັນທຶກຂໍ້ມູນໄວ້ກ່ອນຈຶ່ງເພີ່ມການແປເຂົ້າໄປໄດ້." msgid "No other language available." msgstr "ບໍ່ມີພາສາອື່ນໃຫ້ໃຊ້." msgid "#ERROR" msgstr "" msgid "Translate view" msgstr "ມູມມອງການແປ" ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1745680285.1274436 tryton-7.0.24/tryton/data/locale/lt/0000755000175000017500000000000015003173635015404 5ustar00cedced././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1745680285.1641107 tryton-7.0.24/tryton/data/locale/lt/LC_MESSAGES/0000755000175000017500000000000015003173635017171 5ustar00cedced././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1745680279.0 tryton-7.0.24/tryton/data/locale/lt/LC_MESSAGES/tryton.mo0000644000175000017500000003750715003173627021102 0ustar00cedced?  !2GYa q}( /:>Ww     #%Bhy&  # 1?QWm s}  %>R it   ' 0:?U ] g q{    &+GMPT Ze{    4J Q ]g }   % 6 CNWZ _l   "4 C NZir       ,9Qh}     &6 KWpw       # = S c u     I  !5 !8V!!! ! !!!! !!!"" >"H"P" i"v" " "" "" """(" ###!#%@#7f## ## ### # ## # $ $ $0$@$E$ K$ U$ _$ i$ t$$ $$ $$ $$$$ $$ $%$% ,% 6%D%F%<d%% %%%%%%''' (#(+( D(Q(V(Y(\(`(b(w(((((( ()) )$)-)L)i))))))) )"*+*<*)Q*+{* * * ****+$+ 3+ @+,K+x+++++++++, (,$2, W, x,,,,,,-!-<-K-i-y- --%- - ---.. +.7.F.M. i. s.. ...... ."./,/B/H/_/e/ n/ x/ //#/ //////00 0 '050F0K0Q0d0)l00 0 00 0(001"161(L1u111111'1 2'2.242C2F2c2w22 2 22 222233+3 A3$L3 q3{3 33333 4 4 4$4+4 ;4H4 ^4l4u4 444444445 $525A5a55 55 55 5 555(6/6%H6n6 w666666 677/7A7V7 k7#y77 7 77778+8@8\8 b8l8~888I8"8694N99 9999999990:"G: j: u:)::: : : :;;8%;^;f;,m;;;;!;2;0<8< M< Z<d< z<<<<< <<<==4=<= C=O=W=f={= = === ====>>>>3>*8> c> n>|>+~>D>>0?4? 6?@?E?%d record imported.%d record saved.%d records imported.%d records saved.%s (%s)%s (model name)%s (string)%s%%, .....:All fieldsFields selectedPredefined exportsCreate...Search...A new version is available!A_ttachments...About...ActionActions...AddAdd a note to the recordAdd an attachment to the recordAdd existing recordAdd new profileAdd valueAdd...Align CenterAlign LeftAlign RightAll filesAlways ignore this warning.Application ErrorApplication ShortcutsAre you sure to remove this record?Are you sure to remove those records?Attachments (%s)Attachments...BoldBookmark Name:Bookmark this filterCSV Export: %sCSV Import: %sCSV ParametersC_onnectCancelCancel connection to the Tryton serverCancel savingCheck URL: %sCheck VersionChoose a languageClearClear the field CloseClose TabCollapse all rowsCollapse rowCompareConcurrency ExceptionConnect the Tryton serverCopyCopy URL into clipboardCopy _URL...Copy selected textCreate a new recordCreate a new record Create new relationCreate/Select new lineCreated atCut selected textDatabase:Date FormatDefaultDeleteDelete selected record Delimiter:DigitsDisplay formatDisplayed date formatDisplayed valueDo you want to proceed?DownloadE-Mail...EditEdit User PreferencesEdit...Edited atEdited byEncoding:ErrorExpand all rowsExpand rowExpand/CollapseFalseFavoritesFetching databases listField nameFile to Import:FindForeground ColorFormFormatFuzzyGlobalHeight:HelpHost / Database informationHost:IDID:IconsImage SizeImage size too large.ImagesItalicJustifyKeyboard Shortcuts...Launch actionLimitLimit:Lines to Skip:LinkList/Tree ShortcutsListed RecordsLoginLogs (%s)MManage...Mark line for deletion/removalMark line for removalModel:Move CursorMove downMove down of one pageMove leftMove rightMove to bottomMove to parentMove to topMove upMove up of one pageNameNewNextNext RecordNoNo action defined.No other language available.No result found.Note (%d/%d)Notes (%s)Notes...OKOpenOpen filtersOpen related recordsOpen relationOpen reportOpen the calendarOpen the record Open...Open/Search relationOptionsOverride '%s' definition?PDA ModePNG image (*.png)PastePaste copied textPre-validationPreferencePreferencesPreferences...PreviousPrevious RecordPrintPrint reportPrint...ProfileProfile EditorProfile:QuitQuote char:Record saved.Records not removed.Records removed.RelateRelate...Reload/UndoRemove "%s"Remove Remove selected profileRemove selected recordRemove this bookmarkReportReport BugReport...RevisionRevision:SaveSave AsSave As...Save Tree StateSave this recordSave your current versionSearchSearch %sSearch Limit SettingsSearch Limit...Search a record Search menuSee the modified versionSelectSelect a colorSelect a revisionSelect allSelect parentSelect your actionSelect...Select/Activate current rowSelected RecordsSelectionShortcutsShow active recordsShow bookmarks of filtersShow inactive recordsShow plain textShow revisions...Spell CheckingSubject:SwitchSwitch ViewSwitch viewTextText and IconsThe following action requires to close all tabs. Do you want to continue?The number of decimalThis record has been modified do you want to save it?This record has been modified while you were editing it.To:TodayToggle menuToggle rowToggle selectionToolbarTranslate viewTranslationTrueUnable to set locale %sUnable to set view tree stateUndelete selected record UnderlineUnknownUnmark line for deletionUnselect allUse locale formatUser name:Username:ValueView _Logs...WeekWhat is the name of this export?Width:WizardWorking now on the duplicated record(s).Write AnywayYYesYou have to select one record.You must select an import file first.You need to save the record before adding translations.Your selection:_Actions..._Add_Auto-Detect_Cancel_Clear_Close Tab_Copy URL_Delete Export_Delete..._Duplicate_E-Mail..._Export Data..._Import Data..._New_Next_Notes..._Previous_Print..._Relate..._Reload/Undo_Remove_Report..._Save_Save Export_Search_Switch Viewddevelopment modego backgo forwardhlogging everything at INFO levelmmodularity, scalability and securitynext yearprevious yearsspecify alternate config filespecify the log level: DEBUG, INFO, WARNING, ERROR, CRITICALspecify the login userspecify the server hostname:portttranslator-creditswyProject-Id-Version: PROJECT VERSION Report-Msgid-Bugs-To: EMAIL@ADDRESS POT-Creation-Date: 2025-04-26 17:11+0200 PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE Last-Translator: FULL NAME Language: lt Language-Team: lt Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && (n%100<10 || n%100>=20) ? 1 : 2); MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: 8bit Generated-By: Babel 2.17.0 %d įrašas importuotas.%d įrašas išsaugotas.%d įrašai importuoti.%d įrašai išsaugoti.%s (%s)%s (modelio pavadinimas)%s (eilutė)%s%%, .....:Galimi laukaiPasirinkti laukaiIšsaugoti eksportavimo šablonaiSukurti...Ieškoti...Yra prieinama nauja versija!P_riedas...Apie...VeiksmaiVeiksmai...PridėtiPrisegti pastabą prie įrašoPrisegti failą prie įrašoPridėti turimą įrašąSukurti naują profilįPridėti reikšmęSukurti naują...Centrinė lygiuotėKairinė lygiuotėDešininė lygiuotėVisi failaiDaugiau nepaisyti šio pranešimo.Programos klaidaProgramų šaukiniaiAr tikrai norite ištrinti šį įrašą?Ar tikrai norite ištrinti šiuos įrašus?Priedai (%s)Priedai...PusjuodisŽymelės pavadinimas:Sukurti šio filtro žymelęCSV eksportavimas: %sCSV importas: %sCSV parametrai_PrisijungtiAtsisakytiAtšaukti prisijungimą prie Tryton serverioNutraukti įrašymąPasirinkti URL: %sTikrinti versijąPasirinkti kalbąValytiIšvalyti lauką UžvertiUždaryti kortelęSuspausti visas eilutesSuspausti eilutęPalygintiLygiagretaus duomenų rašymo klaidaPrisijungti prie Tryton serverioKopijuotiKopijuoti URL į iškarpinęKopijuoti URL...Kopijuoti pažymėtą tekstąSukurti naują įrašąSukurti naują įrašą Kurti naują sąryšįSukurti/pasirinkti naują eilutęSukūrimo dataIškirpti pažymėtą tekstąDuomenų bazė:Datos formatasNumatytasisNaikintiPašalinti pasirinktą įrašą Skirtukas:SkaitmenysRodyti formatąRodomas datos formatasRodoma reikšmėAr tikrai norite vykdyti?AtsisiųstiEl. paštas...KeistiKeisti naudotojo nustatymusKeisti...Taisymo dataTaisęs naudotojasKoduotė:KlaidaIšplėsti visas eilutesIšplėsti eilutęIšplėsti/SuspaustiNetiesaMėgstamiGaunamas duomenų bazių sąrašasLauko pavadinimasImportuoti iš failo:RastiPriekinio plano spalvaFormaFormatasNetikslusVisuotinisAukštis:PagalbaServerio / duomenų bazės duomenysServeris:IDID:IkonosPaveikslėlio dydisPaveikslėlis yra per didelis.PaveikslėliaiKursyvasIšlygintiTrumpiniai...Vykdyti veiksmąRibaRiba:Praleisti eilutes:NuorodaSąrašo/Hierarchinio sąrašo šaukiniaiRodomi įrašaiPrisijungtiRąstai (%s)mėn.Valdyti...Pažymėti eilutę trynimui/pašalinimuiPažymėti eilutę pašalinimuiModelis:Paslinkti žymeklįPaslinkti į apačiąPerkelti į apačią per vieną puslapįPaslinkti į kairęPaslinkti į dešinęPerkelti į apačiąPerkelti į motininįPerkelti į viršųPaslinkti į viršųPerkelti į viršų per vieną puslapįPavadinimasNaujasKitasKitas įrašasNeNėra apibrėžtų veiksmų.Nėra kitų kalbų.Nerasta rezultatų.Pastaba (%d/%d)Pastabos (%s)Pastabos...GeraiAtvėrimasAtverti filtrusAtverti susijusius įrašusAtverti sąryšįAtverti ataskaitąAtverti kalendoriųAtverti įrašą Atverti...Atverti/ieškoti sąryšinio įrašoParinktysPerrašyti '%s' apibrėžimą?PDA režimasPNG paveikslėlis (*.png)ĮdėtiĮdėti nukopijuotą tekstąIšankstinis patikrinimasNustatymaiNustatymaiNustatymai...BuvęsBuvęs įrašasSpausdinimasSpausdinti ataskaitąSpausdinti...ProfilisProfilių tvarkymasProfilis:IšeitiCitavimo simbolis:Įrašas išsaugotas.Įrašai nebuvo ištrinti.Įrašai ištrinti.SusijęsSusiję įrašai...Perkrauti/AtšauktiŠalinti "%s"Šalinti Pašalinti pasirinktą profilįPašalinti pasirinktą įrašąŠalinti šią žymelęAtaskaitaPranešti apie klaidąAtaskaita...TaisymasTaisymas:IšsaugotiĮrašyti kaipĮrašyti kaip...Įsiminti hierarchinio sąrašo būsenąIšsaugoti šį įrašąIšsaugoti dabartinę jūsų versijąIeškotiIeškoti %sPaieškos ribojimo nustatymaiPaieškos ribojimas...Ieškoti įrašo Paieškos meniuŽiūrėti pakeistą versijąPasirinktiPasirinkti spalvąPasirinkite taisymąPasirinkti viskąPasirinkti motininįPasirinkite veiksmąPasirinkti...Pasirinkti/Aktyvuoti esamą eilutęPasirinkti įrašaiParinktisŠaukiniaiRodyti aktyvius įrašusRodyti filtrų žymelesRodyti neaktyvius įrašusRodyti paprastą tekstąRodyti pakeitimus...Rašybos klaidų tikrinimasTema:PerjungtiPerjungti rodinįPerjungti rodymąTekstasTekstas ir ikonosAtliekant šį veiksmą bus uždarytos visos kortelės. Ar norite tęsti?Dešimtainių skaitmenų skaičiusŠis įrašas buvo pakeistas Ar norite jį išsaugoti?Kol jūs taisėte šį įrašą, jis buvo pakeistas.Kam:ŠiandienPerjungti meniuSukeisti eilutęSukeisti atrankąĮrankių juostaIšversti rodinįVertimasTiesaNepavyko nustatyti lokalės %sNepavyko nustatyti hierarchinio sąrašo rodinioAtkurti pasirinktą įrašą PabrauktasNežinomasNuimti eilutės pažymėjimą ištrynimuiPanaikinti visą žymėjimąNaudoti lokalės formatąNaudotojas:Naudotojas:ReikšmėŽiūrėti _žurnalus...SavaitėKokiu pavadinimu išsaugoti šį eksportavimo šabloną?Plotis:VedlysDabar dirbate su įrašo(-ų) kopija(-omis).Rašyti visvienm.TaipPasirinkite bent vieną įrašą.Jūs pirmiausiai turite pasirinkti importo failą.Išsaugokite įrašą prieš pridedant vertimą.Jūsų pasirinkimas:_Veiksmai..._PridėtiAptikti automatiškai_AtsisakytiValytiUždaryti _kortelę_Kopijuoti URLIštrinti eksporto šabloną_Ištrinti...Sukurti ko_pijąSiųsti _el. paštu..._Eksportuoti duomenis..._Importuoti duomenis..._Naujas_KitasPastabos..._Buvęs_Spausdinti..._Susiję įrašai...Pe_rkrauti/atšaukti_Ištrinti_Ataskaita..._SaugotiIšsaugoti eksporto šablonąI_eškoti_Perjungti rodymąd.kūrimo veiksenaatgalpirmynval.rašyti į žurnalą INFO lygiumin.moduliškumas, išplečiamumas ir saugumaskiti metaipraeiti metaisnurodyti alternatyvų konfigūravimo failąnurodyti rašymo į žurnalą lygmenį: DEBUG, INFO, ERROR, CRITICALnurodyti naudotojąnurodyti tarnybinę stotį pavadinimas:prievadastMindaugassav.t././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1703673879.0 tryton-7.0.24/tryton/data/locale/lt/LC_MESSAGES/tryton.po0000644000175000017500000005044714543000027021072 0ustar00cedced# Lithuanian (Lithuania) translations for tryton. # Copyright (C) 2011 B2CK # This file is distributed under the same license as the tryton project. # Giedrius Slavinskas , 2011. msgid "" msgstr "Content-Type: text/plain; charset=utf-8\n" msgid "specify alternate config file" msgstr "nurodyti alternatyvų konfigūravimo failą" msgid "development mode" msgstr "kūrimo veiksena" msgid "logging everything at INFO level" msgstr "rašyti į žurnalą INFO lygiu" msgid "specify the log level: DEBUG, INFO, WARNING, ERROR, CRITICAL" msgstr "nurodyti rašymo į žurnalą lygmenį: DEBUG, INFO, ERROR, CRITICAL" msgid "specify the file used to output logging information" msgstr "" msgid "specify the login user" msgstr "nurodyti naudotoją" msgid "specify the server hostname:port" msgstr "nurodyti tarnybinę stotį pavadinimas:prievadas" msgid "disable thread usage" msgstr "" #, python-format msgid "Unable to set locale %s" msgstr "Nepavyko nustatyti lokalės %s" msgid ", " msgstr ", " msgid ",..." msgstr "" #, python-format msgid "%s (%s)" msgstr "%s (%s)" msgid "Select your action" msgstr "Pasirinkite veiksmą" msgid "No action defined." msgstr "Nėra apibrėžtų veiksmų." msgid "By: " msgstr "" msgid "Selection" msgstr "Parinktis" msgid "Cancel" msgstr "Atsisakyti" msgid "OK" msgstr "Gerai" msgid "Your selection:" msgstr "Jūsų pasirinkimas:" msgid "Save As..." msgstr "Įrašyti kaip..." msgid "Do you want to proceed?" msgstr "Ar tikrai norite vykdyti?" msgid "Always ignore this warning." msgstr "Daugiau nepaisyti šio pranešimo." msgid "No" msgstr "Ne" msgid "Yes" msgstr "Taip" msgid "Concurrency Exception" msgstr "Lygiagretaus duomenų rašymo klaida" msgid "This record has been modified while you were editing it." msgstr "Kol jūs taisėte šį įrašą, jis buvo pakeistas." msgid "Cancel saving" msgstr "Nutraukti įrašymą" msgid "Compare" msgstr "Palyginti" msgid "See the modified version" msgstr "Žiūrėti pakeistą versiją" msgid "Write Anyway" msgstr "Rašyti visvien" msgid "Save your current version" msgstr "Išsaugoti dabartinę jūsų versiją" #, fuzzy, python-format msgid "Compare: %s" msgstr "Palyginti: %s" msgid "Close" msgstr "Užverti" msgid "Application Error" msgstr "Programos klaida" msgid "Report Bug" msgstr "Pranešti apie klaidą" #, python-format msgid "Check URL: %s" msgstr "Pasirinkti URL: %s" #, fuzzy msgid "Unable to check for new version." msgstr "Negalima patikrinti naujos versijos" msgid "A new version is available!" msgstr "Yra prieinama nauja versija!" msgid "Download" msgstr "Atsisiųsti" #, fuzzy msgid "Could not get a session." msgstr "Nepavyko prisijungti prie serverio" msgid "Too many requests. Try again later." msgstr "" #, fuzzy msgid "Not found." msgstr "Nerasta rezultatų." #, fuzzy msgid "Not Found." msgstr "Nerasta rezultatų." msgid "..." msgstr "..." msgid "Search..." msgstr "Ieškoti..." msgid "Create..." msgstr "Sukurti..." #, fuzzy, python-format msgid "Create \"%s\"..." msgstr "Sukurti „%s“..." msgid "Value" msgstr "Reikšmė" msgid "Displayed value" msgstr "Rodoma reikšmė" msgid "Format" msgstr "Formatas" msgid "Display format" msgstr "Rodyti formatą" msgid "Open the calendar" msgstr "Atverti kalendorių" msgid "Date Format" msgstr "Datos formatas" msgid "Displayed date format" msgstr "Rodomas datos formatas" msgid "y" msgstr "t" msgid "True" msgstr "Tiesa" msgid "t" msgstr "t" msgid "False" msgstr "Netiesa" msgid "Digits" msgstr "Skaitmenys" msgid "The number of decimal" msgstr "Dešimtainių skaitmenų skaičius" #, fuzzy msgid "Template" msgstr "Susijęs" msgid "Edit..." msgstr "Keisti..." #, fuzzy msgid "View Logs..." msgstr "Žiūrėti _žurnalus..." msgid "Attachments..." msgstr "Priedai..." msgid "Notes..." msgstr "Pastabos..." msgid "Actions..." msgstr "Veiksmai..." msgid "Relate..." msgstr "Susiję įrašai..." msgid "Report..." msgstr "Ataskaita..." msgid "Print..." msgstr "Spausdinti..." msgid "E-Mail..." msgstr "El. paštas..." msgid "Bold" msgstr "Pusjuodis" msgid "Italic" msgstr "Kursyvas" msgid "Underline" msgstr "Pabrauktas" msgid "Align Left" msgstr "Kairinė lygiuotė" msgid "Align Center" msgstr "Centrinė lygiuotė" msgid "Align Right" msgstr "Dešininė lygiuotė" msgid "Justify" msgstr "Išlyginti" msgid "Foreground Color" msgstr "Priekinio plano spalva" msgid "Select a color" msgstr "Pasirinkti spalvą" msgid "Y" msgstr "m." msgid "M" msgstr "mėn." msgid "w" msgstr "sav." msgid "d" msgstr "d." msgid "h" msgstr "val." msgid "m" msgstr "min." msgid "s" msgstr "s" msgid "Preferences..." msgstr "Nustatymai..." msgid "Toolbar" msgstr "Įrankių juosta" msgid "Default" msgstr "Numatytasis" msgid "Text and Icons" msgstr "Tekstas ir ikonos" msgid "Text" msgstr "Tekstas" msgid "Icons" msgstr "Ikonos" msgid "Form" msgstr "Forma" msgid "Save Column Width" msgstr "" msgid "Save Tree State" msgstr "Įsiminti hierarchinio sąrašo būseną" msgid "Spell Checking" msgstr "Rašybos klaidų tikrinimas" msgid "Play Sound for Code Scanner" msgstr "" msgid "PDA Mode" msgstr "PDA režimas" msgid "Search Limit..." msgstr "Paieškos ribojimas..." msgid "Check Version" msgstr "Tikrinti versiją" msgid "Options" msgstr "Parinktys" #, fuzzy msgid "Documentation..." msgstr "Veiksmai..." msgid "Keyboard Shortcuts..." msgstr "Trumpiniai..." msgid "About..." msgstr "Apie..." msgid "Help" msgstr "Pagalba" msgid "No result found." msgstr "Nerasta rezultatų." msgid "Favorites" msgstr "Mėgstami" msgid "Manage..." msgstr "Valdyti..." msgid "Action" msgstr "Veiksmai" msgid "" "The following action requires to close all tabs.\n" "Do you want to continue?" msgstr "" "Atliekant šį veiksmą bus uždarytos visos kortelės.\n" "Ar norite tęsti?" msgid "Application Shortcuts" msgstr "Programų šaukiniai" msgid "Global" msgstr "Visuotinis" msgid "Preferences" msgstr "Nustatymai" msgid "Search menu" msgstr "Paieškos meniu" msgid "Toggle menu" msgstr "Perjungti meniu" #, fuzzy msgid "Previous tab" msgstr "_Buvusi kortelė" #, fuzzy msgid "Next tab" msgstr "Kita kortelė" msgid "Shortcuts" msgstr "Šaukiniai" msgid "Quit" msgstr "Išeiti" msgid "Edition Shortcuts" msgstr "" msgid "Text Entries" msgstr "" msgid "Cut selected text" msgstr "Iškirpti pažymėtą tekstą" msgid "Copy selected text" msgstr "Kopijuoti pažymėtą tekstą" msgid "Paste copied text" msgstr "Įdėti nukopijuotą tekstą" msgid "Next entry" msgstr "" msgid "Previous entry" msgstr "" msgid "Relation Entries" msgstr "" msgid "Create new relation" msgstr "Kurti naują sąryšį" msgid "Open/Search relation" msgstr "Atverti/ieškoti sąryšinio įrašo" msgid "List Entries" msgstr "" msgid "Switch view" msgstr "Perjungti rodymą" msgid "Create/Select new line" msgstr "Sukurti/pasirinkti naują eilutę" msgid "Open relation" msgstr "Atverti sąryšį" msgid "Mark line for deletion/removal" msgstr "Pažymėti eilutę trynimui/pašalinimui" msgid "Mark line for removal" msgstr "Pažymėti eilutę pašalinimui" msgid "Unmark line for deletion" msgstr "Nuimti eilutės pažymėjimą ištrynimui" msgid "List/Tree Shortcuts" msgstr "Sąrašo/Hierarchinio sąrašo šaukiniai" msgid "Move Cursor" msgstr "Paslinkti žymeklį" msgid "Move right" msgstr "Paslinkti į dešinę" msgid "Move left" msgstr "Paslinkti į kairę" msgid "Move up" msgstr "Paslinkti į viršų" msgid "Move down" msgstr "Paslinkti į apačią" msgid "Move up of one page" msgstr "Perkelti į viršų per vieną puslapį" msgid "Move down of one page" msgstr "Perkelti į apačią per vieną puslapį" msgid "Move to top" msgstr "Perkelti į viršų" msgid "Move to bottom" msgstr "Perkelti į apačią" msgid "Move to parent" msgstr "Perkelti į motininį" #, fuzzy msgid "Edition" msgstr "Keisti" #, fuzzy msgid "Copy selected rows" msgstr "Kopijuoti pažymėtą tekstą" msgid "Insert copied rows" msgstr "" msgid "Select all" msgstr "Pasirinkti viską" msgid "Unselect all" msgstr "Panaikinti visą žymėjimą" msgid "Select parent" msgstr "Pasirinkti motininį" msgid "Select/Activate current row" msgstr "Pasirinkti/Aktyvuoti esamą eilutę" msgid "Toggle selection" msgstr "Sukeisti atranką" msgid "Expand/Collapse" msgstr "Išplėsti/Suspausti" msgid "Expand row" msgstr "Išplėsti eilutę" msgid "Collapse row" msgstr "Suspausti eilutę" msgid "Toggle row" msgstr "Sukeisti eilutę" msgid "Collapse all rows" msgstr "Suspausti visas eilutes" msgid "Expand all rows" msgstr "Išplėsti visas eilutes" msgid "Close Tab" msgstr "Uždaryti kortelę" msgid "modularity, scalability and security" msgstr "moduliškumas, išplečiamumas ir saugumas" msgid "translator-credits" msgstr "Mindaugas" #, python-format msgid "Attachments (%s)" msgstr "Priedai (%s)" msgid "Code Scanner" msgstr "" msgid "Code" msgstr "" msgid "Profile Editor" msgstr "Profilių tvarkymas" msgid "Profile" msgstr "Profilis" msgid "Add new profile" msgstr "Sukurti naują profilį" msgid "Remove selected profile" msgstr "Pašalinti pasirinktą profilį" msgid "Host:" msgstr "Serveris:" msgid "Database:" msgstr "Duomenų bazė:" msgid "Fetching databases list" msgstr "Gaunamas duomenų bazių sąrašas" msgid "Username:" msgstr "Naudotojas:" #, fuzzy msgid "Incompatible version of the server." msgstr "Nesuderinama serverio versija" #, fuzzy msgid "Could not connect to the server." msgstr "Nepavyko prisijungti prie serverio" msgid "Login" msgstr "Prisijungti" msgid "_Cancel" msgstr "_Atsisakyti" msgid "Cancel connection to the Tryton server" msgstr "Atšaukti prisijungimą prie Tryton serverio" msgid "C_onnect" msgstr "_Prisijungti" msgid "Connect the Tryton server" msgstr "Prisijungti prie Tryton serverio" msgid "Profile:" msgstr "Profilis:" msgid "Host / Database information" msgstr "Serverio / duomenų bazės duomenys" msgid "User name:" msgstr "Naudotojas:" #, fuzzy msgid "Unable to complete email entry" msgstr "Negalima ištrinti %s vedlio" #, fuzzy, python-format msgid "E-mail %s" msgstr "El. paštas %s" msgid "To:" msgstr "Kam:" #, fuzzy msgid "Cc:" msgstr "CC:" msgid "Bcc:" msgstr "" msgid "Subject:" msgstr "Tema:" #, fuzzy msgid "Body" msgstr "Turinys:" #, fuzzy msgid "Reports" msgstr "Ataskaita" #, fuzzy msgid "Attachments" msgstr "Priedas:" msgid "Files" msgstr "" msgid "Send" msgstr "" #, fuzzy msgid "Select File" msgstr "Pasirinkti viską" #, fuzzy msgid "Remove File" msgstr "Šalinti " msgid "Select" msgstr "Pasirinkti" msgid "Add..." msgstr "Sukurti naują..." #, fuzzy msgid "Preview" msgstr "Buvęs" msgid "Previous" msgstr "Buvęs" msgid "Next" msgstr "Kitas" #, fuzzy, python-format msgid "Attachment (%s)" msgstr "Priedai (%s)" #, python-format msgid "Note (%d/%d)" msgstr "Pastaba (%d/%d)" msgid "You have to select one record." msgstr "Pasirinkite bent vieną įrašą." msgid "Are you sure to remove this record?" msgstr "Ar tikrai norite ištrinti šį įrašą?" msgid "Are you sure to remove those records?" msgstr "Ar tikrai norite ištrinti šiuos įrašus?" msgid "Records not removed." msgstr "Įrašai nebuvo ištrinti." msgid "Records removed." msgstr "Įrašai ištrinti." msgid "Working now on the duplicated record(s)." msgstr "Dabar dirbate su įrašo(-ų) kopija(-omis)." msgid "Record saved." msgstr "Įrašas išsaugotas." msgid "" "This record has been modified\n" "do you want to save it?" msgstr "" "Šis įrašas buvo pakeistas\n" "Ar norite jį išsaugoti?" msgid "Launch action" msgstr "Vykdyti veiksmą" msgid "Relate" msgstr "Susijęs" msgid "Open related records" msgstr "Atverti susijusius įrašus" msgid "Report" msgstr "Ataskaita" msgid "Open report" msgstr "Atverti ataskaitą" msgid "Print" msgstr "Spausdinimas" msgid "Print report" msgstr "Spausdinti ataskaitą" msgid "_Copy URL" msgstr "_Kopijuoti URL" msgid "Copy URL into clipboard" msgstr "Kopijuoti URL į iškarpinę" msgid "Unknown" msgstr "Nežinomas" msgid "Limit" msgstr "Riba" msgid "Search Limit Settings" msgstr "Paieškos ribojimo nustatymai" msgid "Limit:" msgstr "Riba:" #, python-format msgid "Logs (%s)" msgstr "Rąstai (%s)" msgid "Model:" msgstr "Modelis:" msgid "ID:" msgstr "ID:" #, fuzzy msgid "Created by:" msgstr "Sukūręs naudotojas" #, fuzzy msgid "Created at:" msgstr "Sukūrimo data" #, fuzzy msgid "Last Modified by:" msgstr "Paskutinį kartą redagavo:" #, fuzzy msgid "Last Modified at:" msgstr "Paskutinio redagavimo data:" #, python-format msgid "Notes (%s)" msgstr "Pastabos (%s)" msgid "Edit User Preferences" msgstr "Keisti naudotojo nustatymus" msgid "Preference" msgstr "Nustatymai" msgid "Revision" msgstr "Taisymas" msgid "Select a revision" msgstr "Pasirinkite taisymą" msgid "Revision:" msgstr "Taisymas:" msgid "_Switch View" msgstr "_Perjungti rodymą" msgid "Switch View" msgstr "Perjungti rodinį" msgid "_Previous" msgstr "_Buvęs" msgid "Previous Record" msgstr "Buvęs įrašas" msgid "_Next" msgstr "_Kitas" msgid "Next Record" msgstr "Kitas įrašas" msgid "_Search" msgstr "I_eškoti" msgid "_New" msgstr "_Naujas" msgid "Create a new record" msgstr "Sukurti naują įrašą" msgid "_Save" msgstr "_Saugoti" msgid "Save this record" msgstr "Išsaugoti šį įrašą" msgid "_Reload/Undo" msgstr "Pe_rkrauti/atšaukti" msgid "Reload/Undo" msgstr "Perkrauti/Atšaukti" msgid "_Duplicate" msgstr "Sukurti ko_piją" msgid "_Delete..." msgstr "_Ištrinti..." msgid "View _Logs..." msgstr "Žiūrėti _žurnalus..." msgid "Show revisions..." msgstr "Rodyti pakeitimus..." msgid "A_ttachments..." msgstr "P_riedas..." msgid "Add an attachment to the record" msgstr "Prisegti failą prie įrašo" msgid "_Notes..." msgstr "Pastabos..." msgid "Add a note to the record" msgstr "Prisegti pastabą prie įrašo" msgid "_Actions..." msgstr "_Veiksmai..." msgid "_Relate..." msgstr "_Susiję įrašai..." msgid "_Report..." msgstr "_Ataskaita..." msgid "_Print..." msgstr "_Spausdinti..." msgid "_E-Mail..." msgstr "Siųsti _el. paštu..." #, fuzzy msgid "Send an e-mail using the record" msgstr "Prisegti pastabą prie įrašo" msgid "_Export Data..." msgstr "_Eksportuoti duomenis..." msgid "_Import Data..." msgstr "_Importuoti duomenis..." msgid "Copy _URL..." msgstr "Kopijuoti URL..." msgid "_Close Tab" msgstr "Uždaryti _kortelę" msgid "All fields" msgstr "Galimi laukai" msgid "_Add" msgstr "_Pridėti" msgid "_Remove" msgstr "_Ištrinti" msgid "_Clear" msgstr "Valyti" msgid "Fields selected" msgstr "Pasirinkti laukai" msgid "CSV Parameters" msgstr "CSV parametrai" msgid "Delimiter:" msgstr "Skirtukas:" msgid "Quote char:" msgstr "Citavimo simbolis:" msgid "Encoding:" msgstr "Koduotė:" msgid "Use locale format" msgstr "Naudoti lokalės formatą" msgid "Field name" msgstr "Lauko pavadinimas" #, python-format msgid "CSV Export: %s" msgstr "CSV eksportavimas: %s" msgid "_Save Export" msgstr "Išsaugoti eksporto šabloną" #, fuzzy msgid "_URL Export" msgstr "Išsaugoti eksporto šabloną" msgid "_Delete Export" msgstr "Ištrinti eksporto šabloną" msgid "Predefined exports" msgstr "Išsaugoti eksportavimo šablonai" msgid "Name" msgstr "Pavadinimas" msgid "Open" msgstr "Atvėrimas" msgid "Save" msgstr "Išsaugoti" msgid "Listed Records" msgstr "Rodomi įrašai" msgid "Selected Records" msgstr "Pasirinkti įrašai" msgid "Ignore search limit" msgstr "" #, fuzzy msgid "Add field names" msgstr "Pridėti laukų pavadinimus" #, python-format msgid "%s (string)" msgstr "%s (eilutė)" #, python-format msgid "%s (model name)" msgstr "%s (modelio pavadinimas)" #, fuzzy, python-format msgid "%s/Record Name" msgstr "%s (įrašo pavadinimas)" msgid "What is the name of this export?" msgstr "Kokiu pavadinimu išsaugoti šį eksportavimo šabloną?" #, python-format msgid "Override '%s' definition?" msgstr "Perrašyti '%s' apibrėžimą?" #, python-format msgid "%d record saved." msgstr "%d įrašas išsaugotas." #, python-format msgid "%d records saved." msgstr "%d įrašai išsaugoti." msgid "Export failed" msgstr "" msgid "Link" msgstr "Nuoroda" msgid "Delete" msgstr "Naikinti" msgid "Discard changes" msgstr "" msgid "Save and New" msgstr "" #, fuzzy msgid "Add and New" msgstr "Pridėti reikšmę" msgid "Add" msgstr "Pridėti" msgid "Apply changes" msgstr "" msgid "Switch" msgstr "Perjungti" msgid "Remove " msgstr "Šalinti " msgid "Create a new record " msgstr "Sukurti naują įrašą " msgid "Delete selected record " msgstr "Pašalinti pasirinktą įrašą " msgid "Undelete selected record " msgstr "Atkurti pasirinktą įrašą " #, python-format msgid "CSV Import: %s" msgstr "CSV importas: %s" msgid "_Auto-Detect" msgstr "Aptikti automatiškai" msgid "File to Import:" msgstr "Importuoti iš failo:" msgid "Open..." msgstr "Atverti..." msgid "Lines to Skip:" msgstr "Praleisti eilutes:" msgid "You must select an import file first." msgstr "Jūs pirmiausiai turite pasirinkti importo failą." msgid "Detection failed" msgstr "" #, python-format msgid "Unknown column header \"%s\"" msgstr "" msgid "Error" msgstr "Klaida" msgid "Import failed" msgstr "" #, python-format msgid "%d record imported." msgstr "%d įrašas importuotas." #, python-format msgid "%d records imported." msgstr "%d įrašai importuoti." msgid "Search" msgstr "Ieškoti" msgid "New" msgstr "Naujas" #, python-format msgid "Search %s" msgstr "Ieškoti %s" msgid "Wizard" msgstr "Vedlys" msgid "ID" msgstr "ID" #, fuzzy msgid "Created by" msgstr "Sukūręs naudotojas" msgid "Created at" msgstr "Sukūrimo data" msgid "Edited by" msgstr "Taisęs naudotojas" msgid "Edited at" msgstr "Taisymo data" msgid "Unable to set view tree state" msgstr "Nepavyko nustatyti hierarchinio sąrašo rodinio" #, fuzzy, python-format msgid "\"%s\" is not valid according to its domain." msgstr "\"%s\" negalioja šioje srityje" #, fuzzy, python-format msgid "\"%s\" is required." msgstr "Būtina nurodyti \"%s\"" #, fuzzy, python-format msgid "The values of \"%s\" are not valid." msgstr "\"%s\" vertės nėra leistinos" msgid "Pre-validation" msgstr "Išankstinis patikrinimas" msgid ":" msgstr ":" msgid "Scan" msgstr "" msgid "Image Size" msgstr "Paveikslėlio dydis" msgid "Width:" msgstr "Plotis:" msgid "Height:" msgstr "Aukštis:" msgid "PNG image (*.png)" msgstr "PNG paveikslėlis (*.png)" msgid "Save As" msgstr "Įrašyti kaip" msgid "Image size too large." msgstr "Paveikslėlis yra per didelis." msgid "Copy" msgstr "Kopijuoti" msgid "Paste" msgstr "Įdėti" msgid ".." msgstr ".." msgid "Open filters" msgstr "Atverti filtrus" msgid "Show bookmarks of filters" msgstr "Rodyti filtrų žymeles" msgid "Remove this bookmark" msgstr "Šalinti šią žymelę" msgid "Bookmark this filter" msgstr "Sukurti šio filtro žymelę" msgid "Show active records" msgstr "Rodyti aktyvius įrašus" msgid "Show inactive records" msgstr "Rodyti neaktyvius įrašus" msgid "Bookmark Name:" msgstr "Žymelės pavadinimas:" msgid "Find" msgstr "Rasti" msgid "Today" msgstr "Šiandien" msgid "go back" msgstr "atgal" msgid "go forward" msgstr "pirmyn" msgid "previous year" msgstr "praeiti metai" msgid "next year" msgstr "kiti metai" msgid "Day" msgstr "" msgid "Week" msgstr "Savaitė" #, fuzzy msgid "Month" msgstr "Mėnesio rodinys" msgid "Select..." msgstr "Pasirinkti..." msgid "Clear" msgstr "Valyti" msgid "All files" msgstr "Visi failai" msgid "Show plain text" msgstr "Rodyti paprastą tekstą" msgid "Add value" msgstr "Pridėti reikšmę" #, python-format msgid "Remove \"%s\"" msgstr "Šalinti \"%s\"" msgid "Images" msgstr "Paveikslėliai" msgid "Add existing record" msgstr "Pridėti turimą įrašą" msgid "Remove selected record" msgstr "Pašalinti pasirinktą įrašą" msgid "Open the record " msgstr "Atverti įrašą " msgid "Clear the field " msgstr "Išvalyti lauką " msgid "Search a record " msgstr "Ieškoti įrašo " #, fuzzy msgid "Edit selected record" msgstr "Keisti pasirinktą įrašą " #, fuzzy msgid "Delete selected record" msgstr "Pašalinti pasirinktą įrašą" #, python-format msgid "%s%%" msgstr "%s%%" msgid "Choose a language" msgstr "Pasirinkti kalbą" msgid "Translation" msgstr "Vertimas" msgid "Edit" msgstr "Keisti" msgid "Fuzzy" msgstr "Netikslus" msgid "You need to save the record before adding translations." msgstr "Išsaugokite įrašą prieš pridedant vertimą." msgid "No other language available." msgstr "Nėra kitų kalbų." msgid "#ERROR" msgstr "" msgid "Translate view" msgstr "Išversti rodinį" ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1745680285.1274436 tryton-7.0.24/tryton/data/locale/nl/0000755000175000017500000000000015003173635015376 5ustar00cedced././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1745680285.167444 tryton-7.0.24/tryton/data/locale/nl/LC_MESSAGES/0000755000175000017500000000000015003173635017163 5ustar00cedced././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1745680279.0 tryton-7.0.24/tryton/data/locale/nl/LC_MESSAGES/tryton.mo0000644000175000017500000004556315003173627021075 0ustar00cedcedL |*}  %16EHMPTVh  ?K_o      #%4Z jv&  2@ D R`rx     0=P c    &2 D NZ^fm  !2 ; EOTj        & 1 A G L ] b i o v ~         #  !3!:!B!X!j! |!!!!! !!!! !! !!","3" 9" E"O" e" o"z"" """""" " """"#!# 2# =# H# U#`#i#l# q#~# # #######$$.$4$F$b$ q$ |$$$$$$ $$ $$$$%% % !%/%D%U% \%f% w% % % %%%%% % %&& &#&(& 0&;&M& ]&j&{&&& &&&& && ' ''-' ?' J'X' k'u'' ''' ''' ((/(A(P(Y( `( l(x(( ((I((!)5$)8Z))) ) ))#))) * * *2*Q*i** **** ** + + + &+ 3+A+ F+g+n+(u+ ++++%+7+., >,J, O,\,d, k, v,, , , ,,,,, , , , , -- -$- *-7- ?- L-X-Z-k-- -- --$- - ---3.<I.. ......(g0000000111-121A1D1I1L1P1R1e1+111$1 112 2 2#2#C2g2z2222 2 2223!393H3c3/w313 33 3 3 4444#+4O4V4h4z4 44%44444 555(505@5H5X5o5 5 55555566%:6`6 y66666 6 677)7 B7 L7Z7 ^7 h7 t7&777 7788-8 @8J8Z8 c8 m8w888 8 8 8 88 9 99 $929J9]9 d9o999 999 9999999 :&:): -:::M:`: u::::: :::;;,;3;;; M; W;d;;;;; ;(;;< < < <*< D< O<[<j<< <<<<<< <<<<=0=I=X=g= w= === === = == > >%>,> D>N>e>m>#> >> > > >> > > ? ?? -?:?B?Q?Z?_?q??? ????? @@#-@Q@q@@@ @ @@@@ @@@AA*A9APAXA_AgA}AAAA AAAAB B>B RB `BBB B*BBB!B!C@CWCnC C CCCCC CCFCAD%RD7xD=DDD D EE.&EUE^E oEyE&~E%EE)E9F NF[FdF$|FFFFFFFGG G?G HG.RG GGGG*G>G H +H 6HAH XHbHjH zHHH H HHHH H II I-I=I XI eIqIzIIIIIIII IJ J+J-J LJ YJdJ/fJGJAJ K?K]K cKoKqK"%s" is not valid according to its domain."%s" is required.#ERROR%d record imported.%d record saved.%d records imported.%d records saved.%s (%s)%s (model name)%s (string)%s%%%s/Record Name, ,........:All fieldsFields selectedPredefined exportsCreate...Search...A new version is available!A_ttachments...About...ActionActions...AddAdd a note to the recordAdd an attachment to the recordAdd and NewAdd existing recordAdd field namesAdd new profileAdd valueAdd...Align CenterAlign LeftAlign RightAll filesAlways ignore this warning.Application ErrorApplication ShortcutsApply changesAre you sure to remove this record?Are you sure to remove those records?Attachment (%s)AttachmentsAttachments (%s)Attachments...Bcc:BodyBoldBookmark Name:Bookmark this filterBy: CSV Export: %sCSV Import: %sCSV ParametersC_onnectCancelCancel connection to the Tryton serverCancel savingCc:Check URL: %sCheck VersionChoose a languageClearClear the field CloseClose TabCodeCode ScannerCollapse all rowsCollapse rowCompareCompare: %sConcurrency ExceptionConnect the Tryton serverCopyCopy URL into clipboardCopy _URL...Copy selected rowsCopy selected textCould not connect to the server.Could not get a session.Create "%s"...Create a new recordCreate a new record Create new relationCreate/Select new lineCreated atCreated at:Created byCreated by:Cut selected textDatabase:Date FormatDayDefaultDeleteDelete selected recordDelete selected record Delimiter:Detection failedDigitsDiscard changesDisplay formatDisplayed date formatDisplayed valueDo you want to proceed?Documentation...DownloadE-Mail...E-mail %sEditEdit User PreferencesEdit selected recordEdit...Edited atEdited byEditionEdition ShortcutsEncoding:ErrorExpand all rowsExpand rowExpand/CollapseExport failedFalseFavoritesFetching databases listField nameFile to Import:FilesFindForeground ColorFormFormatFuzzyGlobalHeight:HelpHost / Database informationHost:IDID:IconsIgnore search limitImage SizeImage size too large.ImagesImport failedIncompatible version of the server.Insert copied rowsItalicJustifyKeyboard Shortcuts...Last Modified at:Last Modified by:Launch actionLimitLimit:Lines to Skip:LinkList EntriesList/Tree ShortcutsListed RecordsLoginLogs (%s)MManage...Mark line for deletion/removalMark line for removalModel:MonthMove CursorMove downMove down of one pageMove leftMove rightMove to bottomMove to parentMove to topMove upMove up of one pageNameNewNextNext RecordNext entryNext tabNoNo action defined.No other language available.No result found.Not Found.Not found.Note (%d/%d)Notes (%s)Notes...OKOpenOpen filtersOpen related recordsOpen relationOpen reportOpen the calendarOpen the record Open...Open/Search relationOptionsOverride '%s' definition?PDA ModePNG image (*.png)PastePaste copied textPlay Sound for Code ScannerPre-validationPreferencePreferencesPreferences...PreviewPreviousPrevious RecordPrevious entryPrevious tabPrintPrint reportPrint...ProfileProfile EditorProfile:QuitQuote char:Record saved.Records not removed.Records removed.RelateRelate...Relation EntriesReload/UndoRemove "%s"Remove Remove FileRemove selected profileRemove selected recordRemove this bookmarkReportReport BugReport...ReportsRevisionRevision:SaveSave AsSave As...Save Column WidthSave Tree StateSave and NewSave this recordSave your current versionScanSearchSearch %sSearch Limit SettingsSearch Limit...Search a record Search menuSee the modified versionSelectSelect FileSelect a colorSelect a revisionSelect allSelect parentSelect your actionSelect...Select/Activate current rowSelected RecordsSelectionSendSend an e-mail using the recordShortcutsShow active recordsShow bookmarks of filtersShow inactive recordsShow plain textShow revisions...Spell CheckingSubject:SwitchSwitch ViewSwitch viewTemplateTextText EntriesText and IconsThe following action requires to close all tabs. Do you want to continue?The number of decimalThe values of "%s" are not valid.This record has been modified do you want to save it?This record has been modified while you were editing it.To:TodayToggle menuToggle rowToggle selectionToo many requests. Try again later.ToolbarTranslate viewTranslationTrueUnable to check for new version.Unable to complete email entryUnable to set locale %sUnable to set view tree stateUndelete selected record UnderlineUnknownUnknown column header "%s"Unmark line for deletionUnselect allUse locale formatUser name:Username:ValueView Logs...View _Logs...WeekWhat is the name of this export?Width:WizardWorking now on the duplicated record(s).Write AnywayYYesYou have to select one record.You must select an import file first.You need to save the record before adding translations.Your selection:_Actions..._Add_Auto-Detect_Cancel_Clear_Close Tab_Copy URL_Delete Export_Delete..._Duplicate_E-Mail..._Export Data..._Import Data..._New_Next_Notes..._Previous_Print..._Relate..._Reload/Undo_Remove_Report..._Save_Save Export_Search_Switch View_URL Exportddevelopment modedisable thread usagego backgo forwardhlogging everything at INFO levelmmodularity, scalability and securitynext yearprevious yearsspecify alternate config filespecify the file used to output logging informationspecify the log level: DEBUG, INFO, WARNING, ERROR, CRITICALspecify the login userspecify the server hostname:portttranslator-creditswyProject-Id-Version: PROJECT VERSION Report-Msgid-Bugs-To: EMAIL@ADDRESS POT-Creation-Date: 2025-04-26 17:11+0200 PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE Last-Translator: FULL NAME Language: nl Language-Team: nl Plural-Forms: nplurals=2; plural=(n != 1); MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: 8bit Generated-By: Babel 2.17.0 "%s" is niet geldig volgens zijn domein."%s" is vereist.#FOUT%d record geïmporteerd.%d record opgeslagen.%d record geïmporteerd.%d records opgeslagen.%s (%s)%s (modelnaam)%s (tekstregel)%s%%%s/Record Naam, ,........:Alle veldenGeselecteerde veldenVoorgedefinieerde exportinstellingenMaak...Zoek...Er is een nieuwe versie beschikbaar!_Bijlagen...Over...ActieActies...ToevoegenVoeg een notitie toe aan het recordVoeg een bijlage toe aan het recordToevoegen en nieuwBestaand record toevoegenVoeg veldnamen toeNieuw profiel toevoegenWaarde toevoegenToevoegen...CentrerenLinks uitlijnenRechts uitlijnenAlle bestandenDeze waarschuwing altijd negeren.ApplicatiefoutApplicatie snelkoppelingenWijzigingen opslaanWeet u zeker dat u dit record wilt verwijderen?Weet u zeker dat u deze records wilt verwijderen?Bijlage (%s)BijlagenBijlagen (%s)Bijlagen...Bcc:BerichtVetBladwijzer naam:Maak een bladwijzer voor dit filterDoor: Exporteer CSV: %sImporteer CSV: %sCSV parameters_VerbindenAnnuleerVerbreek verbinding met Tryton serverAnnuleer opslaanCc:Controleer URL: %sControleer de versieKies een taalWissenWis het veld SluitenTabblad sluitenBarcodeBarcode scannerAlle rijen samenvouwenrijen samenvouwenVergelijkVergelijk: %sSimultaan gebruik errorVerbind met Tryton serverKopieerKopieer URL naar het klembordKopieer _URL...Kopieer geselecteerde regelsKopieer geselecteerde tekstKan geen verbinding maken met server.Kan geen sessie krijgen.Maak "%s"...Maak een nieuw recordMaak een nieuw record Maak een nieuwe relatieMaak / Selecteer nieuwe regelAanmaakdatumAanmaakdatum:Aangemaakt doorAangemaakt door:Knip geselecteerde tekstDatabase:Datum formaatDagStandaardVerwijderenGeselecteerde record verwijderenGeselecteerde record verwijderen Scheidingsteken:Detecteren faaldeDecimalenWijzigingen ongedaan makenWeergaveformaatWeergegeven datumformaatWeergegeven waardeDoorgaan?Documentatie...DownloadE-mail...E-mail %sBewerkenVoorkeuren gebruiker aanpassenBewerk geselecteerde recordBewerken...Bewerkt opBewerkt doorBewerkingBewerk snelkoppelingenCodering:FoutAlle rijen uitvouwenRij uitvouwenUitvouwen / samenvouwenExporteren misluktOnwaarFavorietenLijst met databases ophalenVeldnaamBestand om te importeren:BestandenZoekenVoorgrondkleurFormulierFormaatOnzekerGlobaalHoogte:HelpHostnaam / database informatieHostnaam:IDID:PictogrammenZoeklimiet negerenAfbeeldingsgrootteAfbeelding te groot.AfbeeldingenImporteren faaldeOnjuiste versie van de server.Gekopieerde regels invoegenCursiefUitvullenSneltoetsen...Laatst gewijzigd om:Laatst aangepast door:Actie uitvoerenLimietLimiet:Regels overslaan:KoppelingLijst invoerLijst- / boomsnelkoppelingenWeergegeven recordsInloggenLogboeken (%s)MBeheren...Markeer regel om te wissen / verwijderenMarkeer regel voor verwijderenModel:MaandBeweeg cursorNaar benedenÉén pagina naar benedenNaar linksNaar rechtsNaar het eindeNaar bovenliggend niveauNaar het beginNaar bovenÉén pagina naar bovenNaamNieuwVolgendeVolgend recordVolgend veldVolgend tabbladNeeGeen actie gedefinieerd.Geen andere taal beschikbaar.Geen resultaat gevonden.Niet gevonden.Niet gevonden.Notitie (%d/%d)Notities (%s)Notities...OKOpenenOpen filtersOpen gerelateerde recordsOpen gerelateerdOpen rapportOpen kalenderOpen het record Openen...Zoek / open gerelateerdOptiesVervang '%s' definitie?PDA-modusPNG afbeelding (*.png)PlakkenPlak geselecteerde tekstGeluid afspelen bij barcode scannerPre-validatieVoorkeurVoorkeurenVoorkeuren...VoorbeeldVorigeVorig recordVorig veldVorig tabbladAfdrukkenRapport afdrukkenAfdrukken...ProfielProfiel-editorProfiel:StopAanhalingstekens:Record opgeslagen.Records niet verwijderd.Records verwijderd.GerelateerdGerelateerd...Gerelateerde veldenOngedaan maken / herladenVerwijder "%s"Verwijder Bestand verwijderenVerwijder het geselecteerde profielGeselecteerd record verwijderenVerwijder deze bladwijzerRapportProgrammafout meldenRapport...RapportenRevisieRevisie:OpslaanOpslaan alsOpslaan als...Kolombreedte opslaanBoomstatus opslaanOpslaan en nieuwRecord opslaanHuidige versie opslaanScannenZoekenZoek %sZoek limiet instellenZoek limiet...Zoek een record ZoekmenuGeef de gewijzigde versie weerSelecteerSelecteer bestandSelecteer een kleurSelecteer een revisieAlles selecterenSelecteer bovenliggend niveauSelecteer een actieSelecteer ...Selecteer / Activeer huidige rijGeselecteerde recordsSelectieVerzendenStuur een e-mail met behulp van het recordSnelkoppelingenGeef actieve records weerBladwijzers van filters weergevenGeef niet-actieve records weerLaat platte tekst zienRevisies weergeven ...Spellingscontrole uitvoerenOnderwerp:OmschakelenWissel van weergaveWissel van weergaveSjabloonTekstTekstinvoerTekst en pictogrammenDe volgende actie vereist het sluiten van alle tabbladen. Verder gaan?Aantal decimalenDe waarden van "%s" zijn niet geldig.Dit record is aangepast. Wilt u de wijzigingen opslaan?Dit record werd gewijzigd terwijl u het aan het bewerken was.Aan:VandaagMenu wisselenWissel rijSelectie wisselenTe veel verzoeken. Probeer het later nog eens.WerkbalkVertaal weergaveVertalingWaarKon niet controleren op nieuwe versie.E-mailinvoer kan niet worden voltooidKan omgeving %s niet instellenKan weergave boomstructuur niet instellenVerwijderen van geselecteerde record ongedaan makenOnderstrepenOnbekendOnbekende kolomkop "%s"Annuleer markering voor verwijderingAlles deselecterenGebruik taalinstellingenGebruikersnaam:Gebruikersnaam:WaardeBekijk Logboek...Bekijk _logboek...WeekWat is de naam voor deze export?Breedte:AssistentAan het werk op het(de) gekopieerde record(s).OverschrijvenJJaU moet één record selecteren.U moet eerst een importbestand selecteren.U moet de record opslaan voordat u vertalingen kunt toevoegen.Uw selectie:_Acties..._Toevoegen_Automatische detectie_Annuleer_WissenTabblad sluiten_Kopieer URL_Exportgegevens verwijderen_Verwijderen..._Dupliceren_E-mail..._Exporteer gegevens..._Importeer gegevens..._Nieuw_Volgende_Notities ...V_orige_Afdrukken..._Gerelateerd..._Ongedaan maken / herladen_Verwijderen_Rapport...Op_slaan_Exportgegevens opslaan_Zoeken_Wissel van weergave_URL exporterendOntwikkelingsmodusschakel gebruik van threads uitga terugga vooruitulogboek bijhouden op INFO niveaummodulair, schaalbaar en veiligvolgend jaarvorig jaarsspecificeer een alternatief configuratiebestandspecificeer het bestand dat wordt gebruikt om loggegevens uit te voerenspecificeer het log niveau: DEBUG, INFO, WARNING, ERROR, CRITICALspecificeer de inlog gebruikerspecificeer de hostnaam:poortw (t)Bert DefoorwJ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685599.0 tryton-7.0.24/tryton/data/locale/nl/LC_MESSAGES/tryton.po0000644000175000017500000004767614517761237021120 0ustar00cedced# Translations template for tryton. # Copyright (C) 2016 Tryton # This file is distributed under the same license as the tryton project. # FIRST AUTHOR , 2016. msgid "" msgstr "Content-Type: text/plain; charset=utf-8\n" msgid "specify alternate config file" msgstr "specificeer een alternatief configuratiebestand" msgid "development mode" msgstr "Ontwikkelingsmodus" msgid "logging everything at INFO level" msgstr "logboek bijhouden op INFO niveau" msgid "specify the log level: DEBUG, INFO, WARNING, ERROR, CRITICAL" msgstr "specificeer het log niveau: DEBUG, INFO, WARNING, ERROR, CRITICAL" msgid "specify the file used to output logging information" msgstr "" "specificeer het bestand dat wordt gebruikt om loggegevens uit te voeren" msgid "specify the login user" msgstr "specificeer de inlog gebruiker" msgid "specify the server hostname:port" msgstr "specificeer de hostnaam:poort" msgid "disable thread usage" msgstr "schakel gebruik van threads uit" #, python-format msgid "Unable to set locale %s" msgstr "Kan omgeving %s niet instellen" msgid ", " msgstr ", " msgid ",..." msgstr ",..." #, python-format msgid "%s (%s)" msgstr "%s (%s)" msgid "Select your action" msgstr "Selecteer een actie" msgid "No action defined." msgstr "Geen actie gedefinieerd." msgid "By: " msgstr "Door: " msgid "Selection" msgstr "Selectie" msgid "Cancel" msgstr "Annuleer" msgid "OK" msgstr "OK" msgid "Your selection:" msgstr "Uw selectie:" msgid "Save As..." msgstr "Opslaan als..." msgid "Do you want to proceed?" msgstr "Doorgaan?" msgid "Always ignore this warning." msgstr "Deze waarschuwing altijd negeren." msgid "No" msgstr "Nee" msgid "Yes" msgstr "Ja" msgid "Concurrency Exception" msgstr "Simultaan gebruik error" msgid "This record has been modified while you were editing it." msgstr "Dit record werd gewijzigd terwijl u het aan het bewerken was." msgid "Cancel saving" msgstr "Annuleer opslaan" msgid "Compare" msgstr "Vergelijk" msgid "See the modified version" msgstr "Geef de gewijzigde versie weer" msgid "Write Anyway" msgstr "Overschrijven" msgid "Save your current version" msgstr "Huidige versie opslaan" #, python-format msgid "Compare: %s" msgstr "Vergelijk: %s" msgid "Close" msgstr "Sluiten" msgid "Application Error" msgstr "Applicatiefout" msgid "Report Bug" msgstr "Programmafout melden" #, python-format msgid "Check URL: %s" msgstr "Controleer URL: %s" msgid "Unable to check for new version." msgstr "Kon niet controleren op nieuwe versie." msgid "A new version is available!" msgstr "Er is een nieuwe versie beschikbaar!" msgid "Download" msgstr "Download" msgid "Could not get a session." msgstr "Kan geen sessie krijgen." msgid "Too many requests. Try again later." msgstr "Te veel verzoeken. Probeer het later nog eens." msgid "Not found." msgstr "Niet gevonden." msgid "Not Found." msgstr "Niet gevonden." msgid "..." msgstr "..." msgid "Search..." msgstr "Zoek..." msgid "Create..." msgstr "Maak..." #, python-format msgid "Create \"%s\"..." msgstr "Maak \"%s\"..." msgid "Value" msgstr "Waarde" msgid "Displayed value" msgstr "Weergegeven waarde" msgid "Format" msgstr "Formaat" msgid "Display format" msgstr "Weergaveformaat" msgid "Open the calendar" msgstr "Open kalender" msgid "Date Format" msgstr "Datum formaat" msgid "Displayed date format" msgstr "Weergegeven datumformaat" msgid "y" msgstr "J" msgid "True" msgstr "Waar" msgid "t" msgstr "w (t)" msgid "False" msgstr "Onwaar" msgid "Digits" msgstr "Decimalen" msgid "The number of decimal" msgstr "Aantal decimalen" msgid "Template" msgstr "Sjabloon" msgid "Edit..." msgstr "Bewerken..." msgid "View Logs..." msgstr "Bekijk Logboek..." msgid "Attachments..." msgstr "Bijlagen..." msgid "Notes..." msgstr "Notities..." msgid "Actions..." msgstr "Acties..." msgid "Relate..." msgstr "Gerelateerd..." msgid "Report..." msgstr "Rapport..." msgid "Print..." msgstr "Afdrukken..." msgid "E-Mail..." msgstr "E-mail..." msgid "Bold" msgstr "Vet" msgid "Italic" msgstr "Cursief" msgid "Underline" msgstr "Onderstrepen" msgid "Align Left" msgstr "Links uitlijnen" msgid "Align Center" msgstr "Centreren" msgid "Align Right" msgstr "Rechts uitlijnen" msgid "Justify" msgstr "Uitvullen" msgid "Foreground Color" msgstr "Voorgrondkleur" msgid "Select a color" msgstr "Selecteer een kleur" msgid "Y" msgstr "J" msgid "M" msgstr "M" msgid "w" msgstr "w" msgid "d" msgstr "d" msgid "h" msgstr "u" msgid "m" msgstr "m" msgid "s" msgstr "s" msgid "Preferences..." msgstr "Voorkeuren..." msgid "Toolbar" msgstr "Werkbalk" msgid "Default" msgstr "Standaard" msgid "Text and Icons" msgstr "Tekst en pictogrammen" msgid "Text" msgstr "Tekst" msgid "Icons" msgstr "Pictogrammen" msgid "Form" msgstr "Formulier" msgid "Save Column Width" msgstr "Kolombreedte opslaan" msgid "Save Tree State" msgstr "Boomstatus opslaan" msgid "Spell Checking" msgstr "Spellingscontrole uitvoeren" msgid "Play Sound for Code Scanner" msgstr "Geluid afspelen bij barcode scanner" msgid "PDA Mode" msgstr "PDA-modus" msgid "Search Limit..." msgstr "Zoek limiet..." msgid "Check Version" msgstr "Controleer de versie" msgid "Options" msgstr "Opties" msgid "Documentation..." msgstr "Documentatie..." msgid "Keyboard Shortcuts..." msgstr "Sneltoetsen..." msgid "About..." msgstr "Over..." msgid "Help" msgstr "Help" msgid "No result found." msgstr "Geen resultaat gevonden." msgid "Favorites" msgstr "Favorieten" msgid "Manage..." msgstr "Beheren..." msgid "Action" msgstr "Actie" msgid "" "The following action requires to close all tabs.\n" "Do you want to continue?" msgstr "" "De volgende actie vereist het sluiten van alle tabbladen.\n" "Verder gaan?" msgid "Application Shortcuts" msgstr "Applicatie snelkoppelingen" msgid "Global" msgstr "Globaal" msgid "Preferences" msgstr "Voorkeuren" msgid "Search menu" msgstr "Zoekmenu" msgid "Toggle menu" msgstr "Menu wisselen" msgid "Previous tab" msgstr "Vorig tabblad" msgid "Next tab" msgstr "Volgend tabblad" msgid "Shortcuts" msgstr "Snelkoppelingen" msgid "Quit" msgstr "Stop" msgid "Edition Shortcuts" msgstr "Bewerk snelkoppelingen" msgid "Text Entries" msgstr "Tekstinvoer" msgid "Cut selected text" msgstr "Knip geselecteerde tekst" msgid "Copy selected text" msgstr "Kopieer geselecteerde tekst" msgid "Paste copied text" msgstr "Plak geselecteerde tekst" msgid "Next entry" msgstr "Volgend veld" msgid "Previous entry" msgstr "Vorig veld" msgid "Relation Entries" msgstr "Gerelateerde velden" msgid "Create new relation" msgstr "Maak een nieuwe relatie" msgid "Open/Search relation" msgstr "Zoek / open gerelateerd" msgid "List Entries" msgstr "Lijst invoer" msgid "Switch view" msgstr "Wissel van weergave" msgid "Create/Select new line" msgstr "Maak / Selecteer nieuwe regel" msgid "Open relation" msgstr "Open gerelateerd" msgid "Mark line for deletion/removal" msgstr "Markeer regel om te wissen / verwijderen" msgid "Mark line for removal" msgstr "Markeer regel voor verwijderen" msgid "Unmark line for deletion" msgstr "Annuleer markering voor verwijdering" msgid "List/Tree Shortcuts" msgstr "Lijst- / boomsnelkoppelingen" msgid "Move Cursor" msgstr "Beweeg cursor" msgid "Move right" msgstr "Naar rechts" msgid "Move left" msgstr "Naar links" msgid "Move up" msgstr "Naar boven" msgid "Move down" msgstr "Naar beneden" msgid "Move up of one page" msgstr "Één pagina naar boven" msgid "Move down of one page" msgstr "Één pagina naar beneden" msgid "Move to top" msgstr "Naar het begin" msgid "Move to bottom" msgstr "Naar het einde" msgid "Move to parent" msgstr "Naar bovenliggend niveau" msgid "Edition" msgstr "Bewerking" msgid "Copy selected rows" msgstr "Kopieer geselecteerde regels" msgid "Insert copied rows" msgstr "Gekopieerde regels invoegen" msgid "Select all" msgstr "Alles selecteren" msgid "Unselect all" msgstr "Alles deselecteren" msgid "Select parent" msgstr "Selecteer bovenliggend niveau" msgid "Select/Activate current row" msgstr "Selecteer / Activeer huidige rij" msgid "Toggle selection" msgstr "Selectie wisselen" msgid "Expand/Collapse" msgstr "Uitvouwen / samenvouwen" msgid "Expand row" msgstr "Rij uitvouwen" msgid "Collapse row" msgstr "rijen samenvouwen" msgid "Toggle row" msgstr "Wissel rij" msgid "Collapse all rows" msgstr "Alle rijen samenvouwen" msgid "Expand all rows" msgstr "Alle rijen uitvouwen" msgid "Close Tab" msgstr "Tabblad sluiten" msgid "modularity, scalability and security" msgstr "modulair, schaalbaar en veilig" msgid "translator-credits" msgstr "Bert Defoor" #, python-format msgid "Attachments (%s)" msgstr "Bijlagen (%s)" msgid "Code Scanner" msgstr "Barcode scanner" msgid "Code" msgstr "Barcode" msgid "Profile Editor" msgstr "Profiel-editor" msgid "Profile" msgstr "Profiel" msgid "Add new profile" msgstr "Nieuw profiel toevoegen" msgid "Remove selected profile" msgstr "Verwijder het geselecteerde profiel" msgid "Host:" msgstr "Hostnaam:" msgid "Database:" msgstr "Database:" msgid "Fetching databases list" msgstr "Lijst met databases ophalen" msgid "Username:" msgstr "Gebruikersnaam:" msgid "Incompatible version of the server." msgstr "Onjuiste versie van de server." msgid "Could not connect to the server." msgstr "Kan geen verbinding maken met server." msgid "Login" msgstr "Inloggen" msgid "_Cancel" msgstr "_Annuleer" msgid "Cancel connection to the Tryton server" msgstr "Verbreek verbinding met Tryton server" msgid "C_onnect" msgstr "_Verbinden" msgid "Connect the Tryton server" msgstr "Verbind met Tryton server" msgid "Profile:" msgstr "Profiel:" msgid "Host / Database information" msgstr "Hostnaam / database informatie" msgid "User name:" msgstr "Gebruikersnaam:" msgid "Unable to complete email entry" msgstr "E-mailinvoer kan niet worden voltooid" #, python-format msgid "E-mail %s" msgstr "E-mail %s" msgid "To:" msgstr "Aan:" msgid "Cc:" msgstr "Cc:" msgid "Bcc:" msgstr "Bcc:" msgid "Subject:" msgstr "Onderwerp:" msgid "Body" msgstr "Bericht" msgid "Reports" msgstr "Rapporten" msgid "Attachments" msgstr "Bijlagen" msgid "Files" msgstr "Bestanden" msgid "Send" msgstr "Verzenden" msgid "Select File" msgstr "Selecteer bestand" msgid "Remove File" msgstr "Bestand verwijderen" msgid "Select" msgstr "Selecteer" msgid "Add..." msgstr "Toevoegen..." msgid "Preview" msgstr "Voorbeeld" msgid "Previous" msgstr "Vorige" msgid "Next" msgstr "Volgende" #, python-format msgid "Attachment (%s)" msgstr "Bijlage (%s)" #, python-format msgid "Note (%d/%d)" msgstr "Notitie (%d/%d)" msgid "You have to select one record." msgstr "U moet één record selecteren." msgid "Are you sure to remove this record?" msgstr "Weet u zeker dat u dit record wilt verwijderen?" msgid "Are you sure to remove those records?" msgstr "Weet u zeker dat u deze records wilt verwijderen?" msgid "Records not removed." msgstr "Records niet verwijderd." msgid "Records removed." msgstr "Records verwijderd." msgid "Working now on the duplicated record(s)." msgstr "Aan het werk op het(de) gekopieerde record(s)." msgid "Record saved." msgstr "Record opgeslagen." msgid "" "This record has been modified\n" "do you want to save it?" msgstr "" "Dit record is aangepast.\n" "Wilt u de wijzigingen opslaan?" msgid "Launch action" msgstr "Actie uitvoeren" msgid "Relate" msgstr "Gerelateerd" msgid "Open related records" msgstr "Open gerelateerde records" msgid "Report" msgstr "Rapport" msgid "Open report" msgstr "Open rapport" msgid "Print" msgstr "Afdrukken" msgid "Print report" msgstr "Rapport afdrukken" msgid "_Copy URL" msgstr "_Kopieer URL" msgid "Copy URL into clipboard" msgstr "Kopieer URL naar het klembord" msgid "Unknown" msgstr "Onbekend" msgid "Limit" msgstr "Limiet" msgid "Search Limit Settings" msgstr "Zoek limiet instellen" msgid "Limit:" msgstr "Limiet:" #, python-format msgid "Logs (%s)" msgstr "Logboeken (%s)" msgid "Model:" msgstr "Model:" msgid "ID:" msgstr "ID:" msgid "Created by:" msgstr "Aangemaakt door:" msgid "Created at:" msgstr "Aanmaakdatum:" msgid "Last Modified by:" msgstr "Laatst aangepast door:" msgid "Last Modified at:" msgstr "Laatst gewijzigd om:" #, python-format msgid "Notes (%s)" msgstr "Notities (%s)" msgid "Edit User Preferences" msgstr "Voorkeuren gebruiker aanpassen" msgid "Preference" msgstr "Voorkeur" msgid "Revision" msgstr "Revisie" msgid "Select a revision" msgstr "Selecteer een revisie" msgid "Revision:" msgstr "Revisie:" msgid "_Switch View" msgstr "_Wissel van weergave" msgid "Switch View" msgstr "Wissel van weergave" msgid "_Previous" msgstr "V_orige" msgid "Previous Record" msgstr "Vorig record" msgid "_Next" msgstr "_Volgende" msgid "Next Record" msgstr "Volgend record" msgid "_Search" msgstr "_Zoeken" msgid "_New" msgstr "_Nieuw" msgid "Create a new record" msgstr "Maak een nieuw record" msgid "_Save" msgstr "Op_slaan" msgid "Save this record" msgstr "Record opslaan" msgid "_Reload/Undo" msgstr "_Ongedaan maken / herladen" msgid "Reload/Undo" msgstr "Ongedaan maken / herladen" msgid "_Duplicate" msgstr "_Dupliceren" msgid "_Delete..." msgstr "_Verwijderen..." msgid "View _Logs..." msgstr "Bekijk _logboek..." msgid "Show revisions..." msgstr "Revisies weergeven ..." msgid "A_ttachments..." msgstr "_Bijlagen..." msgid "Add an attachment to the record" msgstr "Voeg een bijlage toe aan het record" msgid "_Notes..." msgstr "_Notities ..." msgid "Add a note to the record" msgstr "Voeg een notitie toe aan het record" msgid "_Actions..." msgstr "_Acties..." msgid "_Relate..." msgstr "_Gerelateerd..." msgid "_Report..." msgstr "_Rapport..." msgid "_Print..." msgstr "_Afdrukken..." msgid "_E-Mail..." msgstr "_E-mail..." msgid "Send an e-mail using the record" msgstr "Stuur een e-mail met behulp van het record" msgid "_Export Data..." msgstr "_Exporteer gegevens..." msgid "_Import Data..." msgstr "_Importeer gegevens..." msgid "Copy _URL..." msgstr "Kopieer _URL..." msgid "_Close Tab" msgstr "Tabblad sluiten" msgid "All fields" msgstr "Alle velden" msgid "_Add" msgstr "_Toevoegen" msgid "_Remove" msgstr "_Verwijderen" msgid "_Clear" msgstr "_Wissen" msgid "Fields selected" msgstr "Geselecteerde velden" msgid "CSV Parameters" msgstr "CSV parameters" msgid "Delimiter:" msgstr "Scheidingsteken:" msgid "Quote char:" msgstr "Aanhalingstekens:" msgid "Encoding:" msgstr "Codering:" msgid "Use locale format" msgstr "Gebruik taalinstellingen" msgid "Field name" msgstr "Veldnaam" #, python-format msgid "CSV Export: %s" msgstr "Exporteer CSV: %s" msgid "_Save Export" msgstr "_Exportgegevens opslaan" msgid "_URL Export" msgstr "_URL exporteren" msgid "_Delete Export" msgstr "_Exportgegevens verwijderen" msgid "Predefined exports" msgstr "Voorgedefinieerde exportinstellingen" msgid "Name" msgstr "Naam" msgid "Open" msgstr "Openen" msgid "Save" msgstr "Opslaan" msgid "Listed Records" msgstr "Weergegeven records" msgid "Selected Records" msgstr "Geselecteerde records" msgid "Ignore search limit" msgstr "Zoeklimiet negeren" msgid "Add field names" msgstr "Voeg veldnamen toe" #, python-format msgid "%s (string)" msgstr "%s (tekstregel)" #, python-format msgid "%s (model name)" msgstr "%s (modelnaam)" #, python-format msgid "%s/Record Name" msgstr "%s/Record Naam" msgid "What is the name of this export?" msgstr "Wat is de naam voor deze export?" #, python-format msgid "Override '%s' definition?" msgstr "Vervang '%s' definitie?" #, python-format msgid "%d record saved." msgstr "%d record opgeslagen." #, python-format msgid "%d records saved." msgstr "%d records opgeslagen." msgid "Export failed" msgstr "Exporteren mislukt" msgid "Link" msgstr "Koppeling" msgid "Delete" msgstr "Verwijderen" msgid "Discard changes" msgstr "Wijzigingen ongedaan maken" msgid "Save and New" msgstr "Opslaan en nieuw" msgid "Add and New" msgstr "Toevoegen en nieuw" msgid "Add" msgstr "Toevoegen" msgid "Apply changes" msgstr "Wijzigingen opslaan" msgid "Switch" msgstr "Omschakelen" msgid "Remove " msgstr "Verwijder " msgid "Create a new record " msgstr "Maak een nieuw record " msgid "Delete selected record " msgstr "Geselecteerde record verwijderen " msgid "Undelete selected record " msgstr "Verwijderen van geselecteerde record ongedaan maken" #, python-format msgid "CSV Import: %s" msgstr "Importeer CSV: %s" msgid "_Auto-Detect" msgstr "_Automatische detectie" msgid "File to Import:" msgstr "Bestand om te importeren:" msgid "Open..." msgstr "Openen..." msgid "Lines to Skip:" msgstr "Regels overslaan:" msgid "You must select an import file first." msgstr "U moet eerst een importbestand selecteren." msgid "Detection failed" msgstr "Detecteren faalde" #, python-format msgid "Unknown column header \"%s\"" msgstr "Onbekende kolomkop \"%s\"" msgid "Error" msgstr "Fout" msgid "Import failed" msgstr "Importeren faalde" #, python-format msgid "%d record imported." msgstr "%d record geïmporteerd." #, python-format msgid "%d records imported." msgstr "%d record geïmporteerd." msgid "Search" msgstr "Zoeken" msgid "New" msgstr "Nieuw" #, python-format msgid "Search %s" msgstr "Zoek %s" msgid "Wizard" msgstr "Assistent" msgid "ID" msgstr "ID" msgid "Created by" msgstr "Aangemaakt door" msgid "Created at" msgstr "Aanmaakdatum" msgid "Edited by" msgstr "Bewerkt door" msgid "Edited at" msgstr "Bewerkt op" msgid "Unable to set view tree state" msgstr "Kan weergave boomstructuur niet instellen" #, python-format msgid "\"%s\" is not valid according to its domain." msgstr "\"%s\" is niet geldig volgens zijn domein." #, python-format msgid "\"%s\" is required." msgstr "\"%s\" is vereist." #, python-format msgid "The values of \"%s\" are not valid." msgstr "De waarden van \"%s\" zijn niet geldig." msgid "Pre-validation" msgstr "Pre-validatie" msgid ":" msgstr ":" msgid "Scan" msgstr "Scannen" msgid "Image Size" msgstr "Afbeeldingsgrootte" msgid "Width:" msgstr "Breedte:" msgid "Height:" msgstr "Hoogte:" msgid "PNG image (*.png)" msgstr "PNG afbeelding (*.png)" msgid "Save As" msgstr "Opslaan als" msgid "Image size too large." msgstr "Afbeelding te groot." msgid "Copy" msgstr "Kopieer" msgid "Paste" msgstr "Plakken" msgid ".." msgstr ".." msgid "Open filters" msgstr "Open filters" msgid "Show bookmarks of filters" msgstr "Bladwijzers van filters weergeven" msgid "Remove this bookmark" msgstr "Verwijder deze bladwijzer" msgid "Bookmark this filter" msgstr "Maak een bladwijzer voor dit filter" msgid "Show active records" msgstr "Geef actieve records weer" msgid "Show inactive records" msgstr "Geef niet-actieve records weer" msgid "Bookmark Name:" msgstr "Bladwijzer naam:" msgid "Find" msgstr "Zoeken" msgid "Today" msgstr "Vandaag" msgid "go back" msgstr "ga terug" msgid "go forward" msgstr "ga vooruit" msgid "previous year" msgstr "vorig jaar" msgid "next year" msgstr "volgend jaar" msgid "Day" msgstr "Dag" msgid "Week" msgstr "Week" msgid "Month" msgstr "Maand" msgid "Select..." msgstr "Selecteer ..." msgid "Clear" msgstr "Wissen" msgid "All files" msgstr "Alle bestanden" msgid "Show plain text" msgstr "Laat platte tekst zien" msgid "Add value" msgstr "Waarde toevoegen" #, python-format msgid "Remove \"%s\"" msgstr "Verwijder \"%s\"" msgid "Images" msgstr "Afbeeldingen" msgid "Add existing record" msgstr "Bestaand record toevoegen" msgid "Remove selected record" msgstr "Geselecteerd record verwijderen" msgid "Open the record " msgstr "Open het record " msgid "Clear the field " msgstr "Wis het veld " msgid "Search a record " msgstr "Zoek een record " msgid "Edit selected record" msgstr "Bewerk geselecteerde record" msgid "Delete selected record" msgstr "Geselecteerde record verwijderen" #, python-format msgid "%s%%" msgstr "%s%%" msgid "Choose a language" msgstr "Kies een taal" msgid "Translation" msgstr "Vertaling" msgid "Edit" msgstr "Bewerken" msgid "Fuzzy" msgstr "Onzeker" msgid "You need to save the record before adding translations." msgstr "U moet de record opslaan voordat u vertalingen kunt toevoegen." msgid "No other language available." msgstr "Geen andere taal beschikbaar." msgid "#ERROR" msgstr "#FOUT" msgid "Translate view" msgstr "Vertaal weergave" ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1745680285.1274436 tryton-7.0.24/tryton/data/locale/pl/0000755000175000017500000000000015003173635015400 5ustar00cedced././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1745680285.167444 tryton-7.0.24/tryton/data/locale/pl/LC_MESSAGES/0000755000175000017500000000000015003173635017165 5ustar00cedced././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1745680279.0 tryton-7.0.24/tryton/data/locale/pl/LC_MESSAGES/tryton.mo0000644000175000017500000004217215003173627021070 0ustar00cedcedd< \*]   2Ict  - =G N [ f r|#%  &7FKPUdy~&   "(> DN `m u  #<P g r ~   ,<T ] gqv       2=MRchou| #' =KQXg ly       *5D S_g{        # 8 F R d y         !! !)!9! H!U! [!h!q!y!!! ! !!!! !! ! " ""5"L"a" h" s"}"" """ """""" ##$#4# I#U#n#u## # ## ### #$$ ($2$F$`$v$$$$$ $ $$$ $$I$C%!Y%5{%8%%% % & &#&@&H& W&c& h&&&&& &'' ('5' G' R'\' b'p' u'''(' ''''%'7%(]( m(y( ~((( ( (( ( ( (((() ) ) ) () 3)@) H)S) Y)f) n) {)))) )) ))$) ) ***</*l* ******5,, ,,"-1-O-W- g-s-x-{----------.!.0.6.?.E._.|......../"#/F/W/%i/&// ///// 00&0?0G0Z0 k0y00%0 00000 0011'1 ?1 L1 V1%d111111'1232L2e222 22 2 2 2223 "3-333D3]3t33 3 3333 33 44 4)404J4 Y4g4k4t4 4444 4444 44#4#5)5,50565P5`5z555 5555555 66&696 ?6I6 K6Y6v6666!6666!727D7"V7y77 77777777 8 8 '82858=8L8g8y888 888889!9'9>9 Q9 ]9i9 x99999 9 999999 :&:F: b: l:w:: : ::::::;;;';0; 7;C;R;m; ; ;; ;;;<<&<@< H<V<g<x<< < << <<!<==3=M= g=t=== ====== =P=J>#f>5>9>>> ??$?);?e?u? ??1?,?"?0@"L@ o@}@@@@ @ @ @@AA 0A=A-EAsAzA|AAA2AAB B BB #B-BAll fieldsFields selectedPredefined exportsCreate...Search...A new version is available!A_ttachments...About...ActionActions...AddAdd a note to the recordAdd an attachment to the recordAdd existing recordAdd field namesAdd new profileAdd valueAdd...Align CenterAlign LeftAlign RightAll filesAlways ignore this warning.Application ErrorApplication ShortcutsAre you sure to remove this record?Are you sure to remove those records?Attachment (%s)AttachmentsAttachments (%s)Attachments...Bcc:BodyBoldBookmark Name:Bookmark this filterBy: CSV Export: %sCSV Import: %sCSV ParametersC_onnectCancelCancel connection to the Tryton serverCancel savingCc:Check URL: %sCheck VersionChoose a languageClearClear the field CloseClose TabCollapse all rowsCollapse rowCompareCompare: %sConcurrency ExceptionConnect the Tryton serverCopyCopy URL into clipboardCopy _URL...Copy selected textCould not connect to the server.Create a new recordCreate a new record Create new relationCreate/Select new lineCreated atCreated at:Created by:Cut selected textDatabase:Date FormatDefaultDeleteDelete selected recordDelete selected record Delimiter:DigitsDisplay formatDisplayed date formatDisplayed valueDo you want to proceed?DownloadE-Mail...E-mail %sEditEdit User PreferencesEdit selected recordEdit...Edited atEdited byEdition ShortcutsEncoding:ErrorExpand all rowsExpand rowExpand/CollapseFalseFavoritesFetching databases listField nameFile to Import:FindForeground ColorFormFormatFuzzyGlobalHeight:HelpHost / Database informationHost:IDID:IconsIgnore search limitImage SizeImage size too large.ImagesIncompatible version of the server.ItalicJustifyKeyboard Shortcuts...Launch actionLimitLimit:Lines to Skip:LinkList EntriesList/Tree ShortcutsListed RecordsLoginLogs (%s)MManage...Mark line for deletion/removalMark line for removalModel:Move CursorMove downMove down of one pageMove leftMove rightMove to bottomMove to parentMove to topMove upMove up of one pageNameNewNextNext RecordNext entryNext tabNoNo action defined.No other language available.No result found.Note (%d/%d)Notes (%s)Notes...OKOpenOpen filtersOpen related recordsOpen relationOpen reportOpen the calendarOpen the record Open...Open/Search relationOptionsOverride '%s' definition?PDA ModePNG image (*.png)PastePaste copied textPre-validationPreferencePreferencesPreferences...PreviousPrevious RecordPrevious entryPrevious tabPrintPrint reportPrint...ProfileProfile EditorProfile:QuitQuote char:Record saved.Records not removed.Records removed.RelateRelate...Relation EntriesReload/UndoRemove "%s"Remove Remove selected profileRemove selected recordRemove this bookmarkReportReport BugReport...ReportsRevisionRevision:SaveSave AsSave As...Save Column WidthSave Tree StateSave this recordSave your current versionSearchSearch %sSearch Limit SettingsSearch Limit...Search a record Search menuSee the modified versionSelectSelect a colorSelect a revisionSelect allSelect parentSelect your actionSelect...Select/Activate current rowSelected RecordsSelectionSendSend an e-mail using the recordShortcutsShow active recordsShow bookmarks of filtersShow inactive recordsShow plain textShow revisions...Spell CheckingSubject:SwitchSwitch ViewSwitch viewTemplateTextText EntriesText and IconsThe following action requires to close all tabs. Do you want to continue?The number of decimalThe values of "%s" are not valid.This record has been modified do you want to save it?This record has been modified while you were editing it.To:TodayToggle menuToggle rowToggle selectionToo many requests. Try again later.ToolbarTranslate viewTranslationTrueUnable to check for new version.Unable to complete email entryUnable to set locale %sUnable to set view tree stateUndelete selected record UnderlineUnknownUnmark line for deletionUnselect allUse locale formatUser name:Username:ValueView _Logs...WeekWhat is the name of this export?Width:WizardWorking now on the duplicated record(s).Write AnywayYYesYou have to select one record.You must select an import file first.You need to save the record before adding translations.Your selection:_Actions..._Add_Auto-Detect_Cancel_Clear_Close Tab_Copy URL_Delete Export_Delete..._Duplicate_E-Mail..._Export Data..._Import Data..._New_Next_Notes..._Previous_Print..._Relate..._Reload/Undo_Remove_Report..._Save_Save Export_Search_Switch View_URL Exportddevelopment modego backgo forwardhlogging everything at INFO levelmmodularity, scalability and securitynext yearprevious yearsspecify alternate config filespecify the log level: DEBUG, INFO, WARNING, ERROR, CRITICALspecify the login userspecify the server hostname:portttranslator-creditswyProject-Id-Version: PROJECT VERSION Report-Msgid-Bugs-To: EMAIL@ADDRESS POT-Creation-Date: 2025-04-26 17:11+0200 PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE Last-Translator: FULL NAME Language: pl Language-Team: pl Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2); MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: 8bit Generated-By: Babel 2.17.0 "%s" jest nieprawidłowy pod względem swojej domeny."%s" jest wymagany.Rekord %d został zaimportowany.%d rekord został zapisany.Rekordy %d zostały zaimportowane.%d rekordy zostały zapisane.%s (%s)%s (model name)%s (string)%s%%, ,........:Wszystkie polaPola wybranePredefiniowane eksportyUtwórz...Szukaj...Nowa wersja jest dostępna!Załączniki...O programie...AkcjaAkcje...DodajDodaj notatkę do rekorduDodaj załącznik do rekorduDodaj istniejący rekordDodaj nazwy pólDodaj nowy profilDodaj wartośćDodaj...Wyrównanie do środkaWyrównanie do lewejWyrównanie do prawejWszystkie plikiZawsze ignoruj takie ostrzeżenie.Błąd aplikacjiSkróty aplikacjiCzy na pewno usunąć wybrany rekord?Czy na pewno usunąć wybrane rekordy?Załącznik (%s)ZałącznikiZałączniki (%s)Załączniki...UDW:TreśćPogrubionyDodaj nazwę zakładek:Dodaj filtr do zakładekPrzez: Eksport do CSV: %sImport z CSV: %sParametry CSVPołączAnulujAnuluj połączenie z serwerem TrytonAnuluj zapisDW:Sprawdź URL: %sSprawdź wersjęWybierz językWyczyśćWyczyść pole ZamknijZamknij kartęZwiń wszystkie wierszeZwiń wierszPorównajPorównaj: %sWyjątek równoczesnego zapisu danychPołącz z serwerem TrytonKopiujSkopiuj adres URL do schowkaSkopiuj adres URL...Skopiuj zaznaczony tekstNie można połączyć się z serwerem.Utwórz nowy rekordUtwórz nowy rekord Utwórz nowe odniesienieUtwórz/Wybierz nowy wierszData utworzeniaData utworzenia:Utworzył:Wytnij zaznaczony tekstBaza danych:Format datyDomyślnieUsuńUsuń wybrany rekordUsuń zaznaczony rekord Separator:CyfryWyświetl formatWyświetlany format datyWyświetlana wartośćCzy chcesz kontynuować?PobierzE-mail...E-mail %sEdycjaEdytuj preferencje użytkownikaEdytuj wybrany rekordEdycja...Data modyfikacjiZmodyfikowałSkróty edycjiKodowanie:BłądRozwiń wszystkie wierszeRozwiń wierszRozwiń/ZwińNieUlubionePobieranie listy baz danychNazwa polaPlik do zaimportowania:ZnajdźKolor czcionkiFormularzFormatNiejasneGlobalneWysokość:PomocInformacje o hoście / bazie danychHost:IDID:IkonyIgnoruj limit wyszukiwańRozmiar obrazkaZa duży rozmiar obrazka.ObrazkiNiewłaściwa wersja serwera.KursywaWyjustowanieSkróty klawiszowe...Uruchom akcjęLimitLimit:Wiersze do ominięcia:LinkLista wpisówSkróty listy/drzewaWymienione rekordyLoginLogi (%s)MZarządzaj...Zaznacz wiersz do usunięciaZaznacz wiersz do usunięciaModel:Przesuń kursorPrzesuń w dółPrzesuń w dół o jedną stronęPrzesuń w lewoPrzesuń w prawoPrzesuń do dołuPrzesuń do elementu nadrzędnegoPrzesuń do góryPrzesuń w góręPrzesuń w górę o jedną stronęNazwaNowyNastępnyNastępny rekordNastępny wpisNastępna kartaNieBrak zdefiniowanej akcji.Brak innych języków.Nie znaleziono wyniku.Notatka (%d/%d)Notatki (%s)Notatki...OKOtwórzOtwórz filtryOtwórz powiązane rekordyUtwórz odnośnikOtwórz raportOtwórz kalendarzOtwórz rekord Otwórz...Otwórz/Szukaj odniesienieOpcjeCzy nadpisać definicję '%s'?Tryb PDAObrazek w formacie PNG (*.png)WklejWklej zaznaczony tekstWstępna walidacjaPreferencjePreferencjePreferencje...PoprzedniPoprzedni rekordPoprzedni wpisPoprzednia kartaWydrukDrukuj raportWydrukuj...ProfilEdytor profiliProfil:ZakończZnak cudzysłowu:Rekord został zapisany.Rekordy nie zostały usunięte.Rekordy zostały usunięte.OdnośnikOtwórz...Powiązane wpisyOdśwież/PrzywróćUsuń "%s"Usuń Usuń wybrany profilUsuń wybrany rekordUsuń zakładkęRaportZgłoś błądUtwórz raport...RaportyRewizjaRewizja:ZapiszZapisz jakoZapisz jako...Zapisz szerokość kolumnyZapamiętaj stan drzewaZapisz rekordZapisz swoją bieżącą wersjęSzukajSzukaj %sUstawienia limitu wyszukiwaniaOgraniczenie wyszukiwania...Szukaj rekord Menu wyszukiwaniaZobacz zmienioną wersjęWybierzWybierz kolorWybierz rewizjęZaznacz wszystkoZaznacz element nadrzędnyWybierz akcjęWybierz...Zaznacz/Aktywuj bieżący wierszWybrane rekordyZaznaczenieWyślijWyślij e-mail używając rekorduSkrótyPokaż aktywne rekordyPokaż zakładki filtrówPokaż nieaktywne rekordyPokaż tekstPokaż rewizje...Sprawdzanie pisowniTemat:PrzełączPrzełącz widokPrzełącz widokSzablonTekstWpisy tekstoweTekst i ikonyWykonanie polecenia wymaga zamknięcia wszystkich kart. Czy chcesz kontynuować?Liczba miejsc dziesiętnychWartości "%s" nie są prawidłowe.Rekord został zmodyfikowany. Czy chcesz go zapisać?Ten rekord został zmodyfikowany w trakcie twojej edycji.Do:DzisiajMenu bocznePrzełącz wierszPrzełącz zaznaczenieZa dużo połączeń. Spróbuj później.Pasek narzędziWidok tłumaczeniaTłumaczenieTakNie można sprawdzić dostępności nowej wersji.Brak możliwości ukończenia edycji e-mailaNie można ustawić lokalizacji %sBrak możliwości ustawienia stanu widoku drzewaPrzywróć zaznaczony rekord PodkreślenieNieznanyOdznacz wiersz do usunięciaOdznacz wszystkoUżyj formatu lokalnegoUżytkownik:Użytkownik:WartośćPrzeglądaj dziennik...TydzieńJaka jest nazwa tego eksportu?Szerokość:KreatorPracujesz na zduplikowanym(ch) rekordzie(ch).ZapiszRTakMusisz wybrać jeden rekord.Wybierz plik do zaimportowania.Musisz zapisać rekord przed dodaniem tłumaczeń.Twoje zaznaczenie:Akcje...DodajRozpoznajAnulujWyczyśćZamknij kartęSkopiuj adres URLUsuń eksportUsuń...DuplikujE-mail...Eksportuj dane...Importuj dane...NowyNastępnyNotatki...PoprzedniWydruk...Odnośnik...Odśwież/PrzywróćUsuńRaport...ZapiszZapisz eksportSzukajPrzełącz widokEksport URLdtryb deweloperawsteczdalejgrejestrowanie wszystkiego na poziomie INFOmmodułowy, skalowalny i bezpiecznynastępny rokpoprzedni rokspodaj alternatywny plik konfiguracjipodaj poziom rejestrowania: DEBUG, INFO, WARNING, ERROR, CRITICALpodaj login użytkownikapodaj nazwę:port serwerapWojciech Warczakowskitt././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1703673879.0 tryton-7.0.24/tryton/data/locale/pl/LC_MESSAGES/tryton.po0000644000175000017500000004721314543000027021063 0ustar00cedced# Translations template for tryton. # Copyright (C) 2016 Tryton # This file is distributed under the same license as the tryton project. # FIRST AUTHOR , 2016. msgid "" msgstr "Content-Type: text/plain; charset=utf-8\n" msgid "specify alternate config file" msgstr "podaj alternatywny plik konfiguracji" msgid "development mode" msgstr "tryb dewelopera" msgid "logging everything at INFO level" msgstr "rejestrowanie wszystkiego na poziomie INFO" msgid "specify the log level: DEBUG, INFO, WARNING, ERROR, CRITICAL" msgstr "podaj poziom rejestrowania: DEBUG, INFO, WARNING, ERROR, CRITICAL" msgid "specify the file used to output logging information" msgstr "" msgid "specify the login user" msgstr "podaj login użytkownika" msgid "specify the server hostname:port" msgstr "podaj nazwę:port serwera" msgid "disable thread usage" msgstr "" #, python-format msgid "Unable to set locale %s" msgstr "Nie można ustawić lokalizacji %s" msgid ", " msgstr ", " msgid ",..." msgstr ",..." #, python-format msgid "%s (%s)" msgstr "%s (%s)" msgid "Select your action" msgstr "Wybierz akcję" msgid "No action defined." msgstr "Brak zdefiniowanej akcji." msgid "By: " msgstr "Przez: " msgid "Selection" msgstr "Zaznaczenie" msgid "Cancel" msgstr "Anuluj" msgid "OK" msgstr "OK" msgid "Your selection:" msgstr "Twoje zaznaczenie:" msgid "Save As..." msgstr "Zapisz jako..." msgid "Do you want to proceed?" msgstr "Czy chcesz kontynuować?" msgid "Always ignore this warning." msgstr "Zawsze ignoruj takie ostrzeżenie." msgid "No" msgstr "Nie" msgid "Yes" msgstr "Tak" msgid "Concurrency Exception" msgstr "Wyjątek równoczesnego zapisu danych" msgid "This record has been modified while you were editing it." msgstr "Ten rekord został zmodyfikowany w trakcie twojej edycji." msgid "Cancel saving" msgstr "Anuluj zapis" msgid "Compare" msgstr "Porównaj" msgid "See the modified version" msgstr "Zobacz zmienioną wersję" msgid "Write Anyway" msgstr "Zapisz" msgid "Save your current version" msgstr "Zapisz swoją bieżącą wersję" #, python-format msgid "Compare: %s" msgstr "Porównaj: %s" msgid "Close" msgstr "Zamknij" msgid "Application Error" msgstr "Błąd aplikacji" msgid "Report Bug" msgstr "Zgłoś błąd" #, python-format msgid "Check URL: %s" msgstr "Sprawdź URL: %s" msgid "Unable to check for new version." msgstr "Nie można sprawdzić dostępności nowej wersji." msgid "A new version is available!" msgstr "Nowa wersja jest dostępna!" msgid "Download" msgstr "Pobierz" #, fuzzy msgid "Could not get a session." msgstr "Nie można połączyć się z serwerem." msgid "Too many requests. Try again later." msgstr "Za dużo połączeń. Spróbuj później." #, fuzzy msgid "Not found." msgstr "Nie znaleziono wyniku." #, fuzzy msgid "Not Found." msgstr "Nie znaleziono wyniku." msgid "..." msgstr "..." msgid "Search..." msgstr "Szukaj..." msgid "Create..." msgstr "Utwórz..." #, fuzzy, python-format msgid "Create \"%s\"..." msgstr "Utwórz „%s”..." msgid "Value" msgstr "Wartość" msgid "Displayed value" msgstr "Wyświetlana wartość" msgid "Format" msgstr "Format" msgid "Display format" msgstr "Wyświetl format" msgid "Open the calendar" msgstr "Otwórz kalendarz" msgid "Date Format" msgstr "Format daty" msgid "Displayed date format" msgstr "Wyświetlany format daty" msgid "y" msgstr "t" msgid "True" msgstr "Tak" msgid "t" msgstr "p" msgid "False" msgstr "Nie" msgid "Digits" msgstr "Cyfry" msgid "The number of decimal" msgstr "Liczba miejsc dziesiętnych" msgid "Template" msgstr "Szablon" msgid "Edit..." msgstr "Edycja..." #, fuzzy msgid "View Logs..." msgstr "Przeglądaj dziennik..." msgid "Attachments..." msgstr "Załączniki..." msgid "Notes..." msgstr "Notatki..." msgid "Actions..." msgstr "Akcje..." msgid "Relate..." msgstr "Otwórz..." msgid "Report..." msgstr "Utwórz raport..." msgid "Print..." msgstr "Wydrukuj..." msgid "E-Mail..." msgstr "E-mail..." msgid "Bold" msgstr "Pogrubiony" msgid "Italic" msgstr "Kursywa" msgid "Underline" msgstr "Podkreślenie" msgid "Align Left" msgstr "Wyrównanie do lewej" msgid "Align Center" msgstr "Wyrównanie do środka" msgid "Align Right" msgstr "Wyrównanie do prawej" msgid "Justify" msgstr "Wyjustowanie" msgid "Foreground Color" msgstr "Kolor czcionki" msgid "Select a color" msgstr "Wybierz kolor" msgid "Y" msgstr "R" msgid "M" msgstr "M" msgid "w" msgstr "t" msgid "d" msgstr "d" msgid "h" msgstr "g" msgid "m" msgstr "m" msgid "s" msgstr "s" msgid "Preferences..." msgstr "Preferencje..." msgid "Toolbar" msgstr "Pasek narzędzi" msgid "Default" msgstr "Domyślnie" msgid "Text and Icons" msgstr "Tekst i ikony" msgid "Text" msgstr "Tekst" msgid "Icons" msgstr "Ikony" msgid "Form" msgstr "Formularz" msgid "Save Column Width" msgstr "Zapisz szerokość kolumny" msgid "Save Tree State" msgstr "Zapamiętaj stan drzewa" msgid "Spell Checking" msgstr "Sprawdzanie pisowni" msgid "Play Sound for Code Scanner" msgstr "" msgid "PDA Mode" msgstr "Tryb PDA" msgid "Search Limit..." msgstr "Ograniczenie wyszukiwania..." msgid "Check Version" msgstr "Sprawdź wersję" msgid "Options" msgstr "Opcje" #, fuzzy msgid "Documentation..." msgstr "Akcje..." msgid "Keyboard Shortcuts..." msgstr "Skróty klawiszowe..." msgid "About..." msgstr "O programie..." msgid "Help" msgstr "Pomoc" msgid "No result found." msgstr "Nie znaleziono wyniku." msgid "Favorites" msgstr "Ulubione" msgid "Manage..." msgstr "Zarządzaj..." msgid "Action" msgstr "Akcja" msgid "" "The following action requires to close all tabs.\n" "Do you want to continue?" msgstr "" "Wykonanie polecenia wymaga zamknięcia wszystkich kart.\n" "Czy chcesz kontynuować?" msgid "Application Shortcuts" msgstr "Skróty aplikacji" msgid "Global" msgstr "Globalne" msgid "Preferences" msgstr "Preferencje" msgid "Search menu" msgstr "Menu wyszukiwania" msgid "Toggle menu" msgstr "Menu boczne" msgid "Previous tab" msgstr "Poprzednia karta" msgid "Next tab" msgstr "Następna karta" msgid "Shortcuts" msgstr "Skróty" msgid "Quit" msgstr "Zakończ" msgid "Edition Shortcuts" msgstr "Skróty edycji" msgid "Text Entries" msgstr "Wpisy tekstowe" msgid "Cut selected text" msgstr "Wytnij zaznaczony tekst" msgid "Copy selected text" msgstr "Skopiuj zaznaczony tekst" msgid "Paste copied text" msgstr "Wklej zaznaczony tekst" msgid "Next entry" msgstr "Następny wpis" msgid "Previous entry" msgstr "Poprzedni wpis" msgid "Relation Entries" msgstr "Powiązane wpisy" msgid "Create new relation" msgstr "Utwórz nowe odniesienie" msgid "Open/Search relation" msgstr "Otwórz/Szukaj odniesienie" msgid "List Entries" msgstr "Lista wpisów" msgid "Switch view" msgstr "Przełącz widok" msgid "Create/Select new line" msgstr "Utwórz/Wybierz nowy wiersz" msgid "Open relation" msgstr "Utwórz odnośnik" msgid "Mark line for deletion/removal" msgstr "Zaznacz wiersz do usunięcia" msgid "Mark line for removal" msgstr "Zaznacz wiersz do usunięcia" msgid "Unmark line for deletion" msgstr "Odznacz wiersz do usunięcia" msgid "List/Tree Shortcuts" msgstr "Skróty listy/drzewa" msgid "Move Cursor" msgstr "Przesuń kursor" msgid "Move right" msgstr "Przesuń w prawo" msgid "Move left" msgstr "Przesuń w lewo" msgid "Move up" msgstr "Przesuń w górę" msgid "Move down" msgstr "Przesuń w dół" msgid "Move up of one page" msgstr "Przesuń w górę o jedną stronę" msgid "Move down of one page" msgstr "Przesuń w dół o jedną stronę" msgid "Move to top" msgstr "Przesuń do góry" msgid "Move to bottom" msgstr "Przesuń do dołu" msgid "Move to parent" msgstr "Przesuń do elementu nadrzędnego" #, fuzzy msgid "Edition" msgstr "Edycja" #, fuzzy msgid "Copy selected rows" msgstr "Skopiuj zaznaczony tekst" msgid "Insert copied rows" msgstr "" msgid "Select all" msgstr "Zaznacz wszystko" msgid "Unselect all" msgstr "Odznacz wszystko" msgid "Select parent" msgstr "Zaznacz element nadrzędny" msgid "Select/Activate current row" msgstr "Zaznacz/Aktywuj bieżący wiersz" msgid "Toggle selection" msgstr "Przełącz zaznaczenie" msgid "Expand/Collapse" msgstr "Rozwiń/Zwiń" msgid "Expand row" msgstr "Rozwiń wiersz" msgid "Collapse row" msgstr "Zwiń wiersz" msgid "Toggle row" msgstr "Przełącz wiersz" msgid "Collapse all rows" msgstr "Zwiń wszystkie wiersze" msgid "Expand all rows" msgstr "Rozwiń wszystkie wiersze" msgid "Close Tab" msgstr "Zamknij kartę" msgid "modularity, scalability and security" msgstr "modułowy, skalowalny i bezpieczny" msgid "translator-credits" msgstr "Wojciech Warczakowski" #, python-format msgid "Attachments (%s)" msgstr "Załączniki (%s)" msgid "Code Scanner" msgstr "" msgid "Code" msgstr "" msgid "Profile Editor" msgstr "Edytor profili" msgid "Profile" msgstr "Profil" msgid "Add new profile" msgstr "Dodaj nowy profil" msgid "Remove selected profile" msgstr "Usuń wybrany profil" msgid "Host:" msgstr "Host:" msgid "Database:" msgstr "Baza danych:" msgid "Fetching databases list" msgstr "Pobieranie listy baz danych" msgid "Username:" msgstr "Użytkownik:" msgid "Incompatible version of the server." msgstr "Niewłaściwa wersja serwera." msgid "Could not connect to the server." msgstr "Nie można połączyć się z serwerem." msgid "Login" msgstr "Login" msgid "_Cancel" msgstr "Anuluj" msgid "Cancel connection to the Tryton server" msgstr "Anuluj połączenie z serwerem Tryton" msgid "C_onnect" msgstr "Połącz" msgid "Connect the Tryton server" msgstr "Połącz z serwerem Tryton" msgid "Profile:" msgstr "Profil:" msgid "Host / Database information" msgstr "Informacje o hoście / bazie danych" msgid "User name:" msgstr "Użytkownik:" msgid "Unable to complete email entry" msgstr "Brak możliwości ukończenia edycji e-maila" #, python-format msgid "E-mail %s" msgstr "E-mail %s" msgid "To:" msgstr "Do:" msgid "Cc:" msgstr "DW:" msgid "Bcc:" msgstr "UDW:" msgid "Subject:" msgstr "Temat:" msgid "Body" msgstr "Treść" msgid "Reports" msgstr "Raporty" msgid "Attachments" msgstr "Załączniki" msgid "Files" msgstr "" msgid "Send" msgstr "Wyślij" #, fuzzy msgid "Select File" msgstr "Zaznacz wszystko" #, fuzzy msgid "Remove File" msgstr "Usuń " msgid "Select" msgstr "Wybierz" msgid "Add..." msgstr "Dodaj..." #, fuzzy msgid "Preview" msgstr "Poprzedni" msgid "Previous" msgstr "Poprzedni" msgid "Next" msgstr "Następny" #, python-format msgid "Attachment (%s)" msgstr "Załącznik (%s)" #, python-format msgid "Note (%d/%d)" msgstr "Notatka (%d/%d)" msgid "You have to select one record." msgstr "Musisz wybrać jeden rekord." msgid "Are you sure to remove this record?" msgstr "Czy na pewno usunąć wybrany rekord?" msgid "Are you sure to remove those records?" msgstr "Czy na pewno usunąć wybrane rekordy?" msgid "Records not removed." msgstr "Rekordy nie zostały usunięte." msgid "Records removed." msgstr "Rekordy zostały usunięte." msgid "Working now on the duplicated record(s)." msgstr "Pracujesz na zduplikowanym(ch) rekordzie(ch)." msgid "Record saved." msgstr "Rekord został zapisany." msgid "" "This record has been modified\n" "do you want to save it?" msgstr "" "Rekord został zmodyfikowany.\n" "Czy chcesz go zapisać?" msgid "Launch action" msgstr "Uruchom akcję" msgid "Relate" msgstr "Odnośnik" msgid "Open related records" msgstr "Otwórz powiązane rekordy" msgid "Report" msgstr "Raport" msgid "Open report" msgstr "Otwórz raport" msgid "Print" msgstr "Wydruk" msgid "Print report" msgstr "Drukuj raport" msgid "_Copy URL" msgstr "Skopiuj adres URL" msgid "Copy URL into clipboard" msgstr "Skopiuj adres URL do schowka" msgid "Unknown" msgstr "Nieznany" msgid "Limit" msgstr "Limit" msgid "Search Limit Settings" msgstr "Ustawienia limitu wyszukiwania" msgid "Limit:" msgstr "Limit:" #, python-format msgid "Logs (%s)" msgstr "Logi (%s)" msgid "Model:" msgstr "Model:" msgid "ID:" msgstr "ID:" msgid "Created by:" msgstr "Utworzył:" msgid "Created at:" msgstr "Data utworzenia:" #, fuzzy msgid "Last Modified by:" msgstr "Autor ostatniej modyfikacji:" #, fuzzy msgid "Last Modified at:" msgstr "Data ostatniej modyfikacji:" #, python-format msgid "Notes (%s)" msgstr "Notatki (%s)" msgid "Edit User Preferences" msgstr "Edytuj preferencje użytkownika" msgid "Preference" msgstr "Preferencje" msgid "Revision" msgstr "Rewizja" msgid "Select a revision" msgstr "Wybierz rewizję" msgid "Revision:" msgstr "Rewizja:" msgid "_Switch View" msgstr "Przełącz widok" msgid "Switch View" msgstr "Przełącz widok" msgid "_Previous" msgstr "Poprzedni" msgid "Previous Record" msgstr "Poprzedni rekord" msgid "_Next" msgstr "Następny" msgid "Next Record" msgstr "Następny rekord" msgid "_Search" msgstr "Szukaj" msgid "_New" msgstr "Nowy" msgid "Create a new record" msgstr "Utwórz nowy rekord" msgid "_Save" msgstr "Zapisz" msgid "Save this record" msgstr "Zapisz rekord" msgid "_Reload/Undo" msgstr "Odśwież/Przywróć" msgid "Reload/Undo" msgstr "Odśwież/Przywróć" msgid "_Duplicate" msgstr "Duplikuj" msgid "_Delete..." msgstr "Usuń..." msgid "View _Logs..." msgstr "Przeglądaj dziennik..." msgid "Show revisions..." msgstr "Pokaż rewizje..." msgid "A_ttachments..." msgstr "Załączniki..." msgid "Add an attachment to the record" msgstr "Dodaj załącznik do rekordu" msgid "_Notes..." msgstr "Notatki..." msgid "Add a note to the record" msgstr "Dodaj notatkę do rekordu" msgid "_Actions..." msgstr "Akcje..." msgid "_Relate..." msgstr "Odnośnik..." msgid "_Report..." msgstr "Raport..." msgid "_Print..." msgstr "Wydruk..." msgid "_E-Mail..." msgstr "E-mail..." msgid "Send an e-mail using the record" msgstr "Wyślij e-mail używając rekordu" msgid "_Export Data..." msgstr "Eksportuj dane..." msgid "_Import Data..." msgstr "Importuj dane..." msgid "Copy _URL..." msgstr "Skopiuj adres URL..." msgid "_Close Tab" msgstr "Zamknij kartę" msgid "All fields" msgstr "Wszystkie pola" msgid "_Add" msgstr "Dodaj" msgid "_Remove" msgstr "Usuń" msgid "_Clear" msgstr "Wyczyść" msgid "Fields selected" msgstr "Pola wybrane" msgid "CSV Parameters" msgstr "Parametry CSV" msgid "Delimiter:" msgstr "Separator:" msgid "Quote char:" msgstr "Znak cudzysłowu:" msgid "Encoding:" msgstr "Kodowanie:" msgid "Use locale format" msgstr "Użyj formatu lokalnego" msgid "Field name" msgstr "Nazwa pola" #, python-format msgid "CSV Export: %s" msgstr "Eksport do CSV: %s" msgid "_Save Export" msgstr "Zapisz eksport" msgid "_URL Export" msgstr "Eksport URL" msgid "_Delete Export" msgstr "Usuń eksport" msgid "Predefined exports" msgstr "Predefiniowane eksporty" msgid "Name" msgstr "Nazwa" msgid "Open" msgstr "Otwórz" msgid "Save" msgstr "Zapisz" msgid "Listed Records" msgstr "Wymienione rekordy" msgid "Selected Records" msgstr "Wybrane rekordy" msgid "Ignore search limit" msgstr "Ignoruj limit wyszukiwań" msgid "Add field names" msgstr "Dodaj nazwy pól" #, python-format msgid "%s (string)" msgstr "%s (string)" #, python-format msgid "%s (model name)" msgstr "%s (model name)" #, fuzzy, python-format msgid "%s/Record Name" msgstr "%s (record name)" msgid "What is the name of this export?" msgstr "Jaka jest nazwa tego eksportu?" #, python-format msgid "Override '%s' definition?" msgstr "Czy nadpisać definicję '%s'?" #, python-format msgid "%d record saved." msgstr "%d rekord został zapisany." #, python-format msgid "%d records saved." msgstr "%d rekordy zostały zapisane." msgid "Export failed" msgstr "" msgid "Link" msgstr "Link" msgid "Delete" msgstr "Usuń" msgid "Discard changes" msgstr "" msgid "Save and New" msgstr "" #, fuzzy msgid "Add and New" msgstr "Dodaj wartość" msgid "Add" msgstr "Dodaj" msgid "Apply changes" msgstr "" msgid "Switch" msgstr "Przełącz" msgid "Remove " msgstr "Usuń " msgid "Create a new record " msgstr "Utwórz nowy rekord " msgid "Delete selected record " msgstr "Usuń zaznaczony rekord " msgid "Undelete selected record " msgstr "Przywróć zaznaczony rekord " #, python-format msgid "CSV Import: %s" msgstr "Import z CSV: %s" msgid "_Auto-Detect" msgstr "Rozpoznaj" msgid "File to Import:" msgstr "Plik do zaimportowania:" msgid "Open..." msgstr "Otwórz..." msgid "Lines to Skip:" msgstr "Wiersze do ominięcia:" msgid "You must select an import file first." msgstr "Wybierz plik do zaimportowania." msgid "Detection failed" msgstr "" #, python-format msgid "Unknown column header \"%s\"" msgstr "" msgid "Error" msgstr "Błąd" msgid "Import failed" msgstr "" #, python-format msgid "%d record imported." msgstr "Rekord %d został zaimportowany." #, python-format msgid "%d records imported." msgstr "Rekordy %d zostały zaimportowane." msgid "Search" msgstr "Szukaj" msgid "New" msgstr "Nowy" #, python-format msgid "Search %s" msgstr "Szukaj %s" msgid "Wizard" msgstr "Kreator" msgid "ID" msgstr "ID" #, fuzzy msgid "Created by" msgstr "Utworzył:" msgid "Created at" msgstr "Data utworzenia" msgid "Edited by" msgstr "Zmodyfikował" msgid "Edited at" msgstr "Data modyfikacji" msgid "Unable to set view tree state" msgstr "Brak możliwości ustawienia stanu widoku drzewa" #, python-format msgid "\"%s\" is not valid according to its domain." msgstr "\"%s\" jest nieprawidłowy pod względem swojej domeny." #, python-format msgid "\"%s\" is required." msgstr "\"%s\" jest wymagany." #, python-format msgid "The values of \"%s\" are not valid." msgstr "Wartości \"%s\" nie są prawidłowe." msgid "Pre-validation" msgstr "Wstępna walidacja" msgid ":" msgstr ":" msgid "Scan" msgstr "" msgid "Image Size" msgstr "Rozmiar obrazka" msgid "Width:" msgstr "Szerokość:" msgid "Height:" msgstr "Wysokość:" msgid "PNG image (*.png)" msgstr "Obrazek w formacie PNG (*.png)" msgid "Save As" msgstr "Zapisz jako" msgid "Image size too large." msgstr "Za duży rozmiar obrazka." msgid "Copy" msgstr "Kopiuj" msgid "Paste" msgstr "Wklej" msgid ".." msgstr ".." msgid "Open filters" msgstr "Otwórz filtry" msgid "Show bookmarks of filters" msgstr "Pokaż zakładki filtrów" msgid "Remove this bookmark" msgstr "Usuń zakładkę" msgid "Bookmark this filter" msgstr "Dodaj filtr do zakładek" msgid "Show active records" msgstr "Pokaż aktywne rekordy" msgid "Show inactive records" msgstr "Pokaż nieaktywne rekordy" msgid "Bookmark Name:" msgstr "Dodaj nazwę zakładek:" msgid "Find" msgstr "Znajdź" msgid "Today" msgstr "Dzisiaj" msgid "go back" msgstr "wstecz" msgid "go forward" msgstr "dalej" msgid "previous year" msgstr "poprzedni rok" msgid "next year" msgstr "następny rok" msgid "Day" msgstr "" msgid "Week" msgstr "Tydzień" #, fuzzy msgid "Month" msgstr "Widok miesiąca" msgid "Select..." msgstr "Wybierz..." msgid "Clear" msgstr "Wyczyść" msgid "All files" msgstr "Wszystkie pliki" msgid "Show plain text" msgstr "Pokaż tekst" msgid "Add value" msgstr "Dodaj wartość" #, python-format msgid "Remove \"%s\"" msgstr "Usuń \"%s\"" msgid "Images" msgstr "Obrazki" msgid "Add existing record" msgstr "Dodaj istniejący rekord" msgid "Remove selected record" msgstr "Usuń wybrany rekord" msgid "Open the record " msgstr "Otwórz rekord " msgid "Clear the field " msgstr "Wyczyść pole " msgid "Search a record " msgstr "Szukaj rekord " msgid "Edit selected record" msgstr "Edytuj wybrany rekord" msgid "Delete selected record" msgstr "Usuń wybrany rekord" #, python-format msgid "%s%%" msgstr "%s%%" msgid "Choose a language" msgstr "Wybierz język" msgid "Translation" msgstr "Tłumaczenie" msgid "Edit" msgstr "Edycja" msgid "Fuzzy" msgstr "Niejasne" msgid "You need to save the record before adding translations." msgstr "Musisz zapisać rekord przed dodaniem tłumaczeń." msgid "No other language available." msgstr "Brak innych języków." msgid "#ERROR" msgstr "" msgid "Translate view" msgstr "Widok tłumaczenia" ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1745680285.1274436 tryton-7.0.24/tryton/data/locale/pt/0000755000175000017500000000000015003173635015410 5ustar00cedced././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1745680285.167444 tryton-7.0.24/tryton/data/locale/pt/LC_MESSAGES/0000755000175000017500000000000015003173635017175 5ustar00cedced././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1745680279.0 tryton-7.0.24/tryton/data/locale/pt/LC_MESSAGES/tryton.mo0000644000175000017500000003535615003173627021106 0ustar00cedced1 ,-ARgy /?H OZ^w  #%>du&   -;AW ]g y  #7 I S_gn   +5; KVf lv  $+ AOU\k p}       ';@D I U`il    ,4IQkt    &5> C O]r      * 3=B JUev   ) 4B U_ {    , 1>IM58    ' 8 @ O [ ` x      ! !2!9!(@! i!v!x!|!%!7!! "" "'"/" 6" A"K" Z" e" p"{"""" " " " " "" "" "# ###*# 2#=# ?#`#$b# # ###<## $4$6$I$K$M$%% &$&;&C& W&b&g&j&m&q&s&&#&&&& ''' ' *'4'S'r''' ''''(2 (4>( s( (( ((((( ((,)4)=)O)a)h)) )))))))))*8*S*g*******#* ++(+ @+N+g+ n+x+ + +++++++, ,", A,O, f, p,|,,,,,*, ,,,,,, - ----4-<-N-W- g---- -- ----../.@.R.b.~..... ...%./ /+/4/7/ =/K/g/w////// //0*0@0 Q0 ^0l0}000 000 0000011+1D1 W1b1q11 1 1111 22 #212 :2D2 K2W2f2222 2!222 33 03;3M3d3u33 33 3333 4%4>4S4h4q4z44444S4356E5|5555555 5 5:58$6&]6 6666 66 66"67 7/*7Z7m7o7$s787?78 8 ,878 F8P8 X8 d8p8 8 8 8888 8 8 8 8 8 99'969>9S9[9p9r9999$99)9 9 9:1 :S;::%:::::%d record imported.%d record saved.%d records imported.%d records saved.%s (%s)%s (model name)%s (string)%s%%, .....:All fieldsFields selectedPredefined exportsCreate...Search...A new version is available!A_ttachments...About...ActionActions...AddAdd a note to the recordAdd an attachment to the recordAdd existing recordAdd new profileAdd valueAdd...All filesAlways ignore this warning.Application ErrorApplication ShortcutsAre you sure to remove this record?Are you sure to remove those records?Attachments (%s)Attachments...Bookmark Name:Bookmark this filterBy: CSV Export: %sCSV Import: %sCSV ParametersC_onnectCancelCancel connection to the Tryton serverCancel savingCheck URL: %sCheck VersionClearClear the field CloseClose TabCollapse all rowsCollapse rowCompareConcurrency ExceptionConnect the Tryton serverCopy URL into clipboardCopy _URL...Copy selected textCreate a new recordCreate a new record Create new relationCut selected textDatabase:Date FormatDefaultDeleteDelete selected record Delimiter:Display formatDisplayed date formatDisplayed valueDo you want to proceed?DownloadE-Mail...EditEdit User PreferencesEdit...Edition ShortcutsEncoding:ErrorExpand all rowsExpand rowExpand/CollapseFalseFavoritesFetching databases listField nameFile to Import:FindFormFormatFuzzyGlobalHeight:HelpHost / Database informationHost:IDID:IconsImage SizeImage size too large.ImagesKeyboard Shortcuts...Launch actionLimitLimit:Lines to Skip:LinkList EntriesList/Tree ShortcutsLoginLogs (%s)MManage...Model:Move CursorMove downMove down of one pageMove leftMove rightMove to bottomMove to parentMove to topMove upMove up of one pageNameNewNextNext RecordNext entryNext tabNoNo action defined.No other language available.No result found.Notes (%s)Notes...OKOpenOpen filtersOpen related recordsOpen relationOpen reportOpen the calendarOpen the record Open...Open/Search relationOptionsOverride '%s' definition?PDA ModePNG image (*.png)Paste copied textPre-validationPreferencePreferencesPreferences...PreviousPrevious RecordPrevious entryPrevious tabPrintPrint reportPrint...ProfileProfile EditorProfile:QuitQuote char:Record saved.Records not removed.Records removed.RelateRelate...Relation EntriesReload/UndoRemove "%s"Remove Remove selected profileRemove selected recordRemove this bookmarkReportReport BugReport...RevisionRevision:SaveSave AsSave As...Save Tree StateSave this recordSave your current versionSearchSearch %sSearch Limit SettingsSearch Limit...Search a record Search menuSee the modified versionSelectSelect a colorSelect a revisionSelect allSelect parentSelect your actionSelect...Select/Activate current rowSelectionShortcutsShow active recordsShow bookmarks of filtersShow inactive recordsShow plain textShow revisions...Spell CheckingSubject:SwitchSwitch ViewSwitch viewTextText EntriesText and IconsThe following action requires to close all tabs. Do you want to continue?This record has been modified do you want to save it?This record has been modified while you were editing it.To:TodayToggle menuToggle rowToggle selectionToolbarTranslate viewTranslationTrueUnable to set locale %sUnable to set view tree stateUndelete selected record UnknownUnmark line for deletionUnselect allUser name:Username:ValueView _Logs...WeekWhat is the name of this export?Width:WizardWorking now on the duplicated record(s).Write AnywayYYesYou have to select one record.You must select an import file first.You need to save the record before adding translations.Your selection:_Actions..._Add_Auto-Detect_Cancel_Clear_Close Tab_Copy URL_Delete Export_Delete..._Duplicate_E-Mail..._Export Data..._Import Data..._New_Next_Notes..._Previous_Print..._Relate..._Reload/Undo_Remove_Report..._Save_Save Export_Search_Switch Viewddevelopment modego backgo forwardhlogging everything at INFO levelmmodularity, scalability and securitynext yearprevious yearsspecify alternate config filespecify the log level: DEBUG, INFO, WARNING, ERROR, CRITICALspecify the login userspecify the server hostname:portttranslator-creditswyProject-Id-Version: PROJECT VERSION Report-Msgid-Bugs-To: EMAIL@ADDRESS POT-Creation-Date: 2025-04-26 17:11+0200 PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE Last-Translator: FULL NAME Language: pt Language-Team: pt Plural-Forms: nplurals=2; plural=(n != 1); MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: 8bit Generated-By: Babel 2.17.0 %d registros importados.%d registro gravado.%d registros importados.%d registros gravados.%s (%s)%s (nome do modelo)%s (texto)%s%%, .....:Todos os camposCampos selecionadosExportações pré-definidasCriar...Buscar...Nova versão disponível!A_nexos...Sobre...AçãoAções...AdicionarAdicionar uma nota ao registroAdicionar um anexo ao registroAdicionar um registro existenteAdicionar novo perfilAdicionar valorAdicionar...Todos os arquivosSempre ignorar este aviso.Erro no programaAtalhosVocê tem certeza que deseja apagar este registro?Você tem certeza que deseja apagar estes registros?Anexos (%s)Anexos...Nome do favorito:Marcar este filtro como favoritoPor: Exportar CSV: %sImportar CSV: %sParâmetros CSVC_onectarCancelarCancelar a conexão com o servidor do TrytonCancelarVerificar URL: %sVerificar VersãoLimparApagar o registro FecharFechar AbaRecolher todas as linhasRecolher a linhaCompararExceção de concorrênciaConectar ao servidor do TrytonCopiar URL para a área de transferênciaCopiar _URL...Copiar o texto selecionadoCriar novo registroCriar novo registro Criar nova relaçãoRecortar o texto selecionadoBanco de Dados:Formato de DataPadrãoApagarApagar o registro selecionado Delimitador:Formato de exibiçãoFormato de data exibidoValor exibidoVocê deseja prosseguir?BaixarE-mail...EditarEditar preferências do usuárioEditar...Atalhos de EdiçãoCodificação:ErroExpandir todas as linhasExpandir a linhaExpandir/RecolherFalsoFavoritosObtendo a lista de bancos de dadosNome do campoArquivo para importar:LocalizarFormulárioFormatoVagoGlobaisAltura:AjudaInformações do Servidor / Banco de DadosServidor:IDID:ÍconesTamanho da imagemImagem muito grande.ImagensAtalhos...Executar açãoLimiteLimite:Linhas a ignorar:VínculoCampos em listaAtalhos da tela de Lista/ÁrvoreEntrarHistórico (%s)MGerenciar...Modelo:Mover cursorMover para baixoMover uma página para baixoMover para a esquerdaMover para a direitaMover para o finalMover para o paiMover para o topoMover para cimaMover uma página para cimaNomeNovoPróximoPróximo RegistroPróximo campoPróxima AbaNãoNenhuma ação definida.Não há outros idiomas disponíveis.Nenhum resultado encontrado.Notas (%s)Notas...OKAbrirAbrir filtrosAbrir registos relacionadosAbrir relaçãoAbrir relatórioAbrir o calendárioAbrir o registro Abrir...Abrir/Buscar relaçãoOpçõesSobrescrever a definição '%s'?Modo dispositivo portátilImagem PNG (*.png)Colar o texto copiadoPré-validaçãoPreferênciaPreferênciasPreferências...AnteriorRegistro AnteriorCampo anteriorAba AnteriorImprimirImprimir relatórioImprimir...PerfilEditor de PerfilPerfil:SairCaractere de citação:Registo gravado.Registros não apagados.Registos apagados.RelacionarRelacionado...Campos de relaçãoRecarregar/DesfazerRemover "%s"Remover Remover perfil selecionadoRemover o registro selecionadoRemover este favoritoRelatórioReportar erro (bug)Relatório...RevisãoRevisão:GravarSalvar comoSalvar como...Gravar Estado da ÁrvoreGravar este registroGravar sua versão atualBuscaBuscar %sPreferências de limite de buscasLimite de Buscas...Buscar um registro Menu de buscaVer versão modificadaSelecionarSelecione uma corSelecione uma revisãoSelecionar todosSelecionar paiSelecione sua açãoSelecionar...Selecionar/Ativar linha atualSeleçãoAtalhosMostrar registros ativosMostrar filtros favoritosMostrar registros inativosMostrar em formato textoMostrar revisões...Verificar OrtografiaAssunto:AlternarAlternarAlternar exibiçãoTextoCampos de textoTexto e ÍconesA ação seguinte requer que todas as abas sejam fechadas. Você deseja prosseguir?Este registo foi modificado você deseja gravá-lo?Este registro foi modificado enquanto você o editava.Para:HojeMenuAlternar linhaAlternar seleçãoBarra de FerramentasTraduzir visãoTraduçãoVerdadeiroNão foi possível definir as configurações regionais %sNão foi possível definir o estado da visão em árvoreRecuperar o registro selecionado DesconhecidoDesmarcar linha para apagarDesmarcar TodosNome de Usuário:Usuário:ValorVer _Logs...SemanaQual é o nome dessa exportação?Largura:AssistenteTrabalhando agora no(s) registo(s) duplicado(s)Gravar mesmo assimASimVocê precisa selecionar um registo.Primeiro você deve selecionar um arquivo para importar.Você precisa salvar o registro antes de adicionar traduções.Sua seleção:_Ações..._Adicionar_Auto-Detectar_Cancelar_Limpar_Fechar aba_Copiar URL_Apagar exportação_Apagar..._Duplicado_E-Mail..._Exportar Dados..._Importar Dados..._Novo_Próximo_Notas..._Anterior_Imprimir..._Relacionar_Recarregar/Desfazer_Remover_Relatório..._Salvar_Gravar exportação_Buscar_Alternar Exibiçãodmodo de desenvolvimentovoltaravançarhRegistrar (log) tudo com nível INFOmmodularidade, escalabilidade e segurançapróximo anoano anteriorsespecificar arquivo de configuração alternativoEspecifique o nível de registro (log level): DEBUG, INFO, WARNING, ERROR, CRITICALEspecifique o nome do usuárioEspecifique o servidor hostname:portavFelipe Morato Pedross././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1703673879.0 tryton-7.0.24/tryton/data/locale/pt/LC_MESSAGES/tryton.po0000644000175000017500000004747414543000027021104 0ustar00cedced# Translations template for tryton. # Copyright (C) 2016 Tryton # This file is distributed under the same license as the tryton project. # FIRST AUTHOR , 2016. msgid "" msgstr "Content-Type: text/plain; charset=utf-8\n" msgid "specify alternate config file" msgstr "especificar arquivo de configuração alternativo" msgid "development mode" msgstr "modo de desenvolvimento" msgid "logging everything at INFO level" msgstr "Registrar (log) tudo com nível INFO" msgid "specify the log level: DEBUG, INFO, WARNING, ERROR, CRITICAL" msgstr "" "Especifique o nível de registro (log level): DEBUG, INFO, WARNING, ERROR, " "CRITICAL" msgid "specify the file used to output logging information" msgstr "" msgid "specify the login user" msgstr "Especifique o nome do usuário" msgid "specify the server hostname:port" msgstr "Especifique o servidor hostname:porta" msgid "disable thread usage" msgstr "" #, python-format msgid "Unable to set locale %s" msgstr "Não foi possível definir as configurações regionais %s" msgid ", " msgstr ", " msgid ",..." msgstr "" #, python-format msgid "%s (%s)" msgstr "%s (%s)" msgid "Select your action" msgstr "Selecione sua ação" msgid "No action defined." msgstr "Nenhuma ação definida." msgid "By: " msgstr "Por: " msgid "Selection" msgstr "Seleção" msgid "Cancel" msgstr "Cancelar" msgid "OK" msgstr "OK" msgid "Your selection:" msgstr "Sua seleção:" msgid "Save As..." msgstr "Salvar como..." msgid "Do you want to proceed?" msgstr "Você deseja prosseguir?" msgid "Always ignore this warning." msgstr "Sempre ignorar este aviso." msgid "No" msgstr "Não" msgid "Yes" msgstr "Sim" msgid "Concurrency Exception" msgstr "Exceção de concorrência" msgid "This record has been modified while you were editing it." msgstr "Este registro foi modificado enquanto você o editava." msgid "Cancel saving" msgstr "Cancelar" msgid "Compare" msgstr "Comparar" msgid "See the modified version" msgstr "Ver versão modificada" msgid "Write Anyway" msgstr "Gravar mesmo assim" msgid "Save your current version" msgstr "Gravar sua versão atual" #, fuzzy, python-format msgid "Compare: %s" msgstr "Comparar: %s" msgid "Close" msgstr "Fechar" msgid "Application Error" msgstr "Erro no programa" msgid "Report Bug" msgstr "Reportar erro (bug)" #, python-format msgid "Check URL: %s" msgstr "Verificar URL: %s" #, fuzzy msgid "Unable to check for new version." msgstr "Não foi possível verificar por nova versão" msgid "A new version is available!" msgstr "Nova versão disponível!" msgid "Download" msgstr "Baixar" #, fuzzy msgid "Could not get a session." msgstr "Não foi possível se conectar ao servidor" msgid "Too many requests. Try again later." msgstr "" #, fuzzy msgid "Not found." msgstr "Nenhum resultado encontrado." #, fuzzy msgid "Not Found." msgstr "Nenhum resultado encontrado." msgid "..." msgstr "..." msgid "Search..." msgstr "Buscar..." msgid "Create..." msgstr "Criar..." #, fuzzy, python-format msgid "Create \"%s\"..." msgstr "Crie \"%s\"..." msgid "Value" msgstr "Valor" msgid "Displayed value" msgstr "Valor exibido" msgid "Format" msgstr "Formato" msgid "Display format" msgstr "Formato de exibição" msgid "Open the calendar" msgstr "Abrir o calendário" msgid "Date Format" msgstr "Formato de Data" msgid "Displayed date format" msgstr "Formato de data exibido" msgid "y" msgstr "s" msgid "True" msgstr "Verdadeiro" msgid "t" msgstr "v" msgid "False" msgstr "Falso" msgid "Digits" msgstr "" msgid "The number of decimal" msgstr "" #, fuzzy msgid "Template" msgstr "Relacionar" msgid "Edit..." msgstr "Editar..." #, fuzzy msgid "View Logs..." msgstr "Ver _Logs..." msgid "Attachments..." msgstr "Anexos..." msgid "Notes..." msgstr "Notas..." msgid "Actions..." msgstr "Ações..." msgid "Relate..." msgstr "Relacionado..." msgid "Report..." msgstr "Relatório..." msgid "Print..." msgstr "Imprimir..." msgid "E-Mail..." msgstr "E-mail..." msgid "Bold" msgstr "" msgid "Italic" msgstr "" msgid "Underline" msgstr "" msgid "Align Left" msgstr "" msgid "Align Center" msgstr "" msgid "Align Right" msgstr "" msgid "Justify" msgstr "" msgid "Foreground Color" msgstr "" msgid "Select a color" msgstr "Selecione uma cor" msgid "Y" msgstr "A" msgid "M" msgstr "M" msgid "w" msgstr "s" msgid "d" msgstr "d" msgid "h" msgstr "h" msgid "m" msgstr "m" msgid "s" msgstr "s" msgid "Preferences..." msgstr "Preferências..." msgid "Toolbar" msgstr "Barra de Ferramentas" msgid "Default" msgstr "Padrão" msgid "Text and Icons" msgstr "Texto e Ícones" msgid "Text" msgstr "Texto" msgid "Icons" msgstr "Ícones" msgid "Form" msgstr "Formulário" msgid "Save Column Width" msgstr "" msgid "Save Tree State" msgstr "Gravar Estado da Árvore" msgid "Spell Checking" msgstr "Verificar Ortografia" msgid "Play Sound for Code Scanner" msgstr "" msgid "PDA Mode" msgstr "Modo dispositivo portátil" msgid "Search Limit..." msgstr "Limite de Buscas..." msgid "Check Version" msgstr "Verificar Versão" msgid "Options" msgstr "Opções" #, fuzzy msgid "Documentation..." msgstr "Ações..." msgid "Keyboard Shortcuts..." msgstr "Atalhos..." msgid "About..." msgstr "Sobre..." msgid "Help" msgstr "Ajuda" msgid "No result found." msgstr "Nenhum resultado encontrado." msgid "Favorites" msgstr "Favoritos" msgid "Manage..." msgstr "Gerenciar..." msgid "Action" msgstr "Ação" msgid "" "The following action requires to close all tabs.\n" "Do you want to continue?" msgstr "" "A ação seguinte requer que todas as abas sejam fechadas.\n" "Você deseja prosseguir?" msgid "Application Shortcuts" msgstr "Atalhos" msgid "Global" msgstr "Globais" msgid "Preferences" msgstr "Preferências" msgid "Search menu" msgstr "Menu de busca" msgid "Toggle menu" msgstr "Menu" msgid "Previous tab" msgstr "Aba Anterior" msgid "Next tab" msgstr "Próxima Aba" msgid "Shortcuts" msgstr "Atalhos" msgid "Quit" msgstr "Sair" msgid "Edition Shortcuts" msgstr "Atalhos de Edição" msgid "Text Entries" msgstr "Campos de texto" msgid "Cut selected text" msgstr "Recortar o texto selecionado" msgid "Copy selected text" msgstr "Copiar o texto selecionado" msgid "Paste copied text" msgstr "Colar o texto copiado" msgid "Next entry" msgstr "Próximo campo" msgid "Previous entry" msgstr "Campo anterior" msgid "Relation Entries" msgstr "Campos de relação" msgid "Create new relation" msgstr "Criar nova relação" msgid "Open/Search relation" msgstr "Abrir/Buscar relação" msgid "List Entries" msgstr "Campos em lista" msgid "Switch view" msgstr "Alternar exibição" msgid "Create/Select new line" msgstr "" msgid "Open relation" msgstr "Abrir relação" msgid "Mark line for deletion/removal" msgstr "" msgid "Mark line for removal" msgstr "" msgid "Unmark line for deletion" msgstr "Desmarcar linha para apagar" msgid "List/Tree Shortcuts" msgstr "Atalhos da tela de Lista/Árvore" msgid "Move Cursor" msgstr "Mover cursor" msgid "Move right" msgstr "Mover para a direita" msgid "Move left" msgstr "Mover para a esquerda" msgid "Move up" msgstr "Mover para cima" msgid "Move down" msgstr "Mover para baixo" msgid "Move up of one page" msgstr "Mover uma página para cima" msgid "Move down of one page" msgstr "Mover uma página para baixo" msgid "Move to top" msgstr "Mover para o topo" msgid "Move to bottom" msgstr "Mover para o final" msgid "Move to parent" msgstr "Mover para o pai" #, fuzzy msgid "Edition" msgstr "Editar" #, fuzzy msgid "Copy selected rows" msgstr "Copiar o texto selecionado" msgid "Insert copied rows" msgstr "" msgid "Select all" msgstr "Selecionar todos" msgid "Unselect all" msgstr "Desmarcar Todos" msgid "Select parent" msgstr "Selecionar pai" msgid "Select/Activate current row" msgstr "Selecionar/Ativar linha atual" msgid "Toggle selection" msgstr "Alternar seleção" msgid "Expand/Collapse" msgstr "Expandir/Recolher" msgid "Expand row" msgstr "Expandir a linha" msgid "Collapse row" msgstr "Recolher a linha" msgid "Toggle row" msgstr "Alternar linha" msgid "Collapse all rows" msgstr "Recolher todas as linhas" msgid "Expand all rows" msgstr "Expandir todas as linhas" msgid "Close Tab" msgstr "Fechar Aba" msgid "modularity, scalability and security" msgstr "modularidade, escalabilidade e segurança" msgid "translator-credits" msgstr "" "Felipe Morato\n" "Pedro" #, python-format msgid "Attachments (%s)" msgstr "Anexos (%s)" msgid "Code Scanner" msgstr "" msgid "Code" msgstr "" msgid "Profile Editor" msgstr "Editor de Perfil" msgid "Profile" msgstr "Perfil" msgid "Add new profile" msgstr "Adicionar novo perfil" msgid "Remove selected profile" msgstr "Remover perfil selecionado" msgid "Host:" msgstr "Servidor:" msgid "Database:" msgstr "Banco de Dados:" msgid "Fetching databases list" msgstr "Obtendo a lista de bancos de dados" msgid "Username:" msgstr "Usuário:" #, fuzzy msgid "Incompatible version of the server." msgstr "Versão do servidor incompatível" #, fuzzy msgid "Could not connect to the server." msgstr "Não foi possível se conectar ao servidor" msgid "Login" msgstr "Entrar" msgid "_Cancel" msgstr "_Cancelar" msgid "Cancel connection to the Tryton server" msgstr "Cancelar a conexão com o servidor do Tryton" msgid "C_onnect" msgstr "C_onectar" msgid "Connect the Tryton server" msgstr "Conectar ao servidor do Tryton" msgid "Profile:" msgstr "Perfil:" msgid "Host / Database information" msgstr "Informações do Servidor / Banco de Dados" msgid "User name:" msgstr "Nome de Usuário:" #, fuzzy msgid "Unable to complete email entry" msgstr "Não é possível apagar o assistente %s" #, fuzzy, python-format msgid "E-mail %s" msgstr "Email %s" msgid "To:" msgstr "Para:" #, fuzzy msgid "Cc:" msgstr "CC:" msgid "Bcc:" msgstr "" msgid "Subject:" msgstr "Assunto:" #, fuzzy msgid "Body" msgstr "Conteúdo:" #, fuzzy msgid "Reports" msgstr "Relatório" #, fuzzy msgid "Attachments" msgstr "Anexo:" msgid "Files" msgstr "" msgid "Send" msgstr "" #, fuzzy msgid "Select File" msgstr "Selecionar todos" #, fuzzy msgid "Remove File" msgstr "Remover " msgid "Select" msgstr "Selecionar" msgid "Add..." msgstr "Adicionar..." #, fuzzy msgid "Preview" msgstr "Anterior" msgid "Previous" msgstr "Anterior" msgid "Next" msgstr "Próximo" #, fuzzy, python-format msgid "Attachment (%s)" msgstr "Anexos (%s)" #, python-format msgid "Note (%d/%d)" msgstr "" msgid "You have to select one record." msgstr "Você precisa selecionar um registo." msgid "Are you sure to remove this record?" msgstr "Você tem certeza que deseja apagar este registro?" msgid "Are you sure to remove those records?" msgstr "Você tem certeza que deseja apagar estes registros?" msgid "Records not removed." msgstr "Registros não apagados." msgid "Records removed." msgstr "Registos apagados." msgid "Working now on the duplicated record(s)." msgstr "Trabalhando agora no(s) registo(s) duplicado(s)" msgid "Record saved." msgstr "Registo gravado." msgid "" "This record has been modified\n" "do you want to save it?" msgstr "" "Este registo foi modificado\n" "você deseja gravá-lo?" msgid "Launch action" msgstr "Executar ação" msgid "Relate" msgstr "Relacionar" msgid "Open related records" msgstr "Abrir registos relacionados" msgid "Report" msgstr "Relatório" msgid "Open report" msgstr "Abrir relatório" msgid "Print" msgstr "Imprimir" msgid "Print report" msgstr "Imprimir relatório" msgid "_Copy URL" msgstr "_Copiar URL" msgid "Copy URL into clipboard" msgstr "Copiar URL para a área de transferência" msgid "Unknown" msgstr "Desconhecido" msgid "Limit" msgstr "Limite" msgid "Search Limit Settings" msgstr "Preferências de limite de buscas" msgid "Limit:" msgstr "Limite:" #, python-format msgid "Logs (%s)" msgstr "Histórico (%s)" msgid "Model:" msgstr "Modelo:" msgid "ID:" msgstr "ID:" #, fuzzy msgid "Created by:" msgstr "Data de criação" #, fuzzy msgid "Created at:" msgstr "Data de criação" #, fuzzy msgid "Last Modified by:" msgstr "Última Modificação por:" #, fuzzy msgid "Last Modified at:" msgstr "Data da última modificação:" #, python-format msgid "Notes (%s)" msgstr "Notas (%s)" msgid "Edit User Preferences" msgstr "Editar preferências do usuário" msgid "Preference" msgstr "Preferência" msgid "Revision" msgstr "Revisão" msgid "Select a revision" msgstr "Selecione uma revisão" msgid "Revision:" msgstr "Revisão:" msgid "_Switch View" msgstr "_Alternar Exibição" msgid "Switch View" msgstr "Alternar" msgid "_Previous" msgstr "_Anterior" msgid "Previous Record" msgstr "Registro Anterior" msgid "_Next" msgstr "_Próximo" msgid "Next Record" msgstr "Próximo Registro" msgid "_Search" msgstr "_Buscar" msgid "_New" msgstr "_Novo" msgid "Create a new record" msgstr "Criar novo registro" msgid "_Save" msgstr "_Salvar" msgid "Save this record" msgstr "Gravar este registro" msgid "_Reload/Undo" msgstr "_Recarregar/Desfazer" msgid "Reload/Undo" msgstr "Recarregar/Desfazer" msgid "_Duplicate" msgstr "_Duplicado" msgid "_Delete..." msgstr "_Apagar..." msgid "View _Logs..." msgstr "Ver _Logs..." msgid "Show revisions..." msgstr "Mostrar revisões..." msgid "A_ttachments..." msgstr "A_nexos..." msgid "Add an attachment to the record" msgstr "Adicionar um anexo ao registro" msgid "_Notes..." msgstr "_Notas..." msgid "Add a note to the record" msgstr "Adicionar uma nota ao registro" msgid "_Actions..." msgstr "_Ações..." msgid "_Relate..." msgstr "_Relacionar" msgid "_Report..." msgstr "_Relatório..." msgid "_Print..." msgstr "_Imprimir..." msgid "_E-Mail..." msgstr "_E-Mail..." #, fuzzy msgid "Send an e-mail using the record" msgstr "Adicionar uma nota ao registro" msgid "_Export Data..." msgstr "_Exportar Dados..." msgid "_Import Data..." msgstr "_Importar Dados..." msgid "Copy _URL..." msgstr "Copiar _URL..." msgid "_Close Tab" msgstr "_Fechar aba" msgid "All fields" msgstr "Todos os campos" msgid "_Add" msgstr "_Adicionar" msgid "_Remove" msgstr "_Remover" msgid "_Clear" msgstr "_Limpar" msgid "Fields selected" msgstr "Campos selecionados" msgid "CSV Parameters" msgstr "Parâmetros CSV" msgid "Delimiter:" msgstr "Delimitador:" msgid "Quote char:" msgstr "Caractere de citação:" msgid "Encoding:" msgstr "Codificação:" msgid "Use locale format" msgstr "" msgid "Field name" msgstr "Nome do campo" #, python-format msgid "CSV Export: %s" msgstr "Exportar CSV: %s" msgid "_Save Export" msgstr "_Gravar exportação" #, fuzzy msgid "_URL Export" msgstr "_Gravar exportação" msgid "_Delete Export" msgstr "_Apagar exportação" msgid "Predefined exports" msgstr "Exportações pré-definidas" msgid "Name" msgstr "Nome" msgid "Open" msgstr "Abrir" msgid "Save" msgstr "Gravar" #, fuzzy msgid "Listed Records" msgstr "Editar o registro selecionado " #, fuzzy msgid "Selected Records" msgstr "Editar o registro selecionado " msgid "Ignore search limit" msgstr "" #, fuzzy msgid "Add field names" msgstr "Adicionar _nomes de campos" #, python-format msgid "%s (string)" msgstr "%s (texto)" #, python-format msgid "%s (model name)" msgstr "%s (nome do modelo)" #, fuzzy, python-format msgid "%s/Record Name" msgstr "%s (nome do registro)" msgid "What is the name of this export?" msgstr "Qual é o nome dessa exportação?" #, python-format msgid "Override '%s' definition?" msgstr "Sobrescrever a definição '%s'?" #, python-format msgid "%d record saved." msgstr "%d registro gravado." #, python-format msgid "%d records saved." msgstr "%d registros gravados." msgid "Export failed" msgstr "" msgid "Link" msgstr "Vínculo" msgid "Delete" msgstr "Apagar" msgid "Discard changes" msgstr "" msgid "Save and New" msgstr "" #, fuzzy msgid "Add and New" msgstr "Adicionar valor" msgid "Add" msgstr "Adicionar" msgid "Apply changes" msgstr "" msgid "Switch" msgstr "Alternar" msgid "Remove " msgstr "Remover " msgid "Create a new record " msgstr "Criar novo registro " msgid "Delete selected record " msgstr "Apagar o registro selecionado " msgid "Undelete selected record " msgstr "Recuperar o registro selecionado " #, python-format msgid "CSV Import: %s" msgstr "Importar CSV: %s" msgid "_Auto-Detect" msgstr "_Auto-Detectar" msgid "File to Import:" msgstr "Arquivo para importar:" msgid "Open..." msgstr "Abrir..." msgid "Lines to Skip:" msgstr "Linhas a ignorar:" msgid "You must select an import file first." msgstr "Primeiro você deve selecionar um arquivo para importar." msgid "Detection failed" msgstr "" #, python-format msgid "Unknown column header \"%s\"" msgstr "" msgid "Error" msgstr "Erro" msgid "Import failed" msgstr "" #, python-format msgid "%d record imported." msgstr "%d registros importados." #, python-format msgid "%d records imported." msgstr "%d registros importados." msgid "Search" msgstr "Busca" msgid "New" msgstr "Novo" #, python-format msgid "Search %s" msgstr "Buscar %s" msgid "Wizard" msgstr "Assistente" msgid "ID" msgstr "ID" #, fuzzy msgid "Created by" msgstr "Data de criação" #, fuzzy msgid "Created at" msgstr "Data de criação" #, fuzzy msgid "Edited by" msgstr "Editar" #, fuzzy msgid "Edited at" msgstr "Editar" msgid "Unable to set view tree state" msgstr "Não foi possível definir o estado da visão em árvore" #, fuzzy, python-format msgid "\"%s\" is not valid according to its domain." msgstr "\"%s\" não é válido de acordo com seu domínio" #, fuzzy, python-format msgid "\"%s\" is required." msgstr "\"%s\" é obrigatório" #, fuzzy, python-format msgid "The values of \"%s\" are not valid." msgstr "Os valores de \"%s\" não são válidos" msgid "Pre-validation" msgstr "Pré-validação" msgid ":" msgstr ":" msgid "Scan" msgstr "" msgid "Image Size" msgstr "Tamanho da imagem" msgid "Width:" msgstr "Largura:" msgid "Height:" msgstr "Altura:" msgid "PNG image (*.png)" msgstr "Imagem PNG (*.png)" msgid "Save As" msgstr "Salvar como" msgid "Image size too large." msgstr "Imagem muito grande." msgid "Copy" msgstr "" msgid "Paste" msgstr "" msgid ".." msgstr ".." msgid "Open filters" msgstr "Abrir filtros" msgid "Show bookmarks of filters" msgstr "Mostrar filtros favoritos" msgid "Remove this bookmark" msgstr "Remover este favorito" msgid "Bookmark this filter" msgstr "Marcar este filtro como favorito" msgid "Show active records" msgstr "Mostrar registros ativos" msgid "Show inactive records" msgstr "Mostrar registros inativos" msgid "Bookmark Name:" msgstr "Nome do favorito:" msgid "Find" msgstr "Localizar" msgid "Today" msgstr "Hoje" msgid "go back" msgstr "voltar" msgid "go forward" msgstr "avançar" msgid "previous year" msgstr "ano anterior" msgid "next year" msgstr "próximo ano" msgid "Day" msgstr "" msgid "Week" msgstr "Semana" #, fuzzy msgid "Month" msgstr "Visão Mensal" msgid "Select..." msgstr "Selecionar..." msgid "Clear" msgstr "Limpar" msgid "All files" msgstr "Todos os arquivos" msgid "Show plain text" msgstr "Mostrar em formato texto" msgid "Add value" msgstr "Adicionar valor" #, python-format msgid "Remove \"%s\"" msgstr "Remover \"%s\"" msgid "Images" msgstr "Imagens" msgid "Add existing record" msgstr "Adicionar um registro existente" msgid "Remove selected record" msgstr "Remover o registro selecionado" msgid "Open the record " msgstr "Abrir o registro " msgid "Clear the field " msgstr "Apagar o registro " msgid "Search a record " msgstr "Buscar um registro " #, fuzzy msgid "Edit selected record" msgstr "Editar o registro selecionado " #, fuzzy msgid "Delete selected record" msgstr "Remover o registro selecionado" #, python-format msgid "%s%%" msgstr "%s%%" msgid "Choose a language" msgstr "" msgid "Translation" msgstr "Tradução" msgid "Edit" msgstr "Editar" msgid "Fuzzy" msgstr "Vago" msgid "You need to save the record before adding translations." msgstr "Você precisa salvar o registro antes de adicionar traduções." msgid "No other language available." msgstr "Não há outros idiomas disponíveis." msgid "#ERROR" msgstr "" msgid "Translate view" msgstr "Traduzir visão" ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1745680285.1274436 tryton-7.0.24/tryton/data/locale/ro/0000755000175000017500000000000015003173635015405 5ustar00cedced././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1745680285.167444 tryton-7.0.24/tryton/data/locale/ro/LC_MESSAGES/0000755000175000017500000000000015003173635017172 5ustar00cedced././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1745680279.0 tryton-7.0.24/tryton/data/locale/ro/LC_MESSAGES/tryton.mo0000644000175000017500000004545615003173627021105 0ustar00cedced| *!5F[mu  7GP Wbf     .@#V%z #2AJ&Q x     -GL dq  ) 4 @ KW i s 6G P Zdi      # ;FV\arw~   # 5 H O W m         ! ! !+!A!H! N! Z!d! z! !!! !!!!!! ! !!"""6" G" R" ]" j"u"~"" "" " """""##(#1#C#I#[# j# u###### ## ####$ $ $ $($=$N$ U$_$ p$ |$ $ $$$$$ $ $% % %%!% )%4%F%V%g%% %%%% %%% %&& && 1&?& R&\&x& &&& &&&&''('7'@' G' S'_'h' m'z'I''!'5 (8A(z(~( ( ((#((( (( ()8)P)n) ))) )) ) )) ) ) * *3*:*(A* j*w*y*}*%*7** ++ +(+0+ 7+ B+L+ [+ f+ q+|++++ + + + + ++ ++ +, , ,$,&,7,L, T,_, a,,$, , ,,,3,<-R- i------+c//////00"0 20>0C0]0`0e0h0l0n00000#01 1 1 )151#=1)a1"1111 11 22,2)=2g2 |2(2(22 2 33*3/343C3T3c3h3w3 3 33,333344(4/4 H4S4c4}4 4444445505*F5q55"5555 66 6%6;6J6Y6\6 e6"o6*6 66666 7777H7 Q7 [7e7m7!7 7 7 7 77 778 818 E8S8X8!a8888888888 89) 959=9@9 D9O9l9$}99 9$9999:: /:=:N:V:_: r:}::: : :: :*: ;9;@; F; T;b;v;;;;;;;;;;<"<;<K<N<%h<<< < < <<< << =+=?=Q=e= ==== ==== > > *>6>F>U>^>x>>>> >>>> >>?& ?#G?k? r?|??????"?@'@.@ @@M@ T@ ^@i@ q@~@@@@@@ @A!A8AWA"fA AAAA AAA B!B0B IBSB,[B B BB BBCC3CAll fieldsFields selectedPredefined exportsCreate...Search...A new version is available!A_ttachments...About...ActionActions...AddAdd a note to the recordAdd an attachment to the recordAdd existing recordAdd field namesAdd new profileAdd valueAdd...Align CenterAlign LeftAlign RightAll filesAlways ignore this warning.Application ErrorApplication ShortcutsAre you sure to remove this record?Are you sure to remove those records?Attachment (%s)AttachmentsAttachments (%s)Attachments...Bcc:BodyBoldBookmark Name:Bookmark this filterBy: CSV Export: %sCSV Import: %sCSV ParametersC_onnectCancelCancel connection to the Tryton serverCancel savingCc:Check URL: %sCheck VersionChoose a languageClearClear the field CloseClose TabCollapse all rowsCollapse rowCompareCompare: %sConcurrency ExceptionConnect the Tryton serverCopyCopy URL into clipboardCopy _URL...Copy selected rowsCopy selected textCould not connect to the server.Could not get a session.Create a new recordCreate a new record Create new relationCreate/Select new lineCreated atCreated at:Created byCreated by:Cut selected textDatabase:Date FormatDayDefaultDeleteDelete selected recordDelete selected record Delimiter:Detection failedDigitsDisplay formatDisplayed date formatDisplayed valueDo you want to proceed?Documentation...DownloadE-Mail...E-mail %sEditEdit User PreferencesEdit selected recordEdit...Edited atEdited byEditionEdition ShortcutsEncoding:ErrorExpand all rowsExpand rowExpand/CollapseExport failedFalseFavoritesFetching databases listField nameFile to Import:FilesFindForeground ColorFormFormatFuzzyGlobalHeight:HelpHost / Database informationHost:IDID:IconsIgnore search limitImage SizeImage size too large.ImagesImport failedIncompatible version of the server.Insert copied rowsItalicJustifyKeyboard Shortcuts...Last Modified at:Last Modified by:Launch actionLimitLimit:Lines to Skip:LinkList EntriesList/Tree ShortcutsListed RecordsLoginLogs (%s)MManage...Mark line for deletion/removalMark line for removalModel:MonthMove CursorMove downMove down of one pageMove leftMove rightMove to bottomMove to parentMove to topMove upMove up of one pageNameNewNextNext RecordNext entryNext tabNoNo action defined.No other language available.No result found.Not Found.Not found.Note (%d/%d)Notes (%s)Notes...OKOpenOpen filtersOpen related recordsOpen relationOpen reportOpen the calendarOpen the record Open...Open/Search relationOptionsOverride '%s' definition?PDA ModePNG image (*.png)PastePaste copied textPre-validationPreferencePreferencesPreferences...PreviewPreviousPrevious RecordPrevious entryPrevious tabPrintPrint reportPrint...ProfileProfile EditorProfile:QuitQuote char:Record saved.Records not removed.Records removed.RelateRelate...Relation EntriesReload/UndoRemove "%s"Remove Remove FileRemove selected profileRemove selected recordRemove this bookmarkReportReport BugReport...ReportsRevisionRevision:SaveSave AsSave As...Save Column WidthSave Tree StateSave this recordSave your current versionSearchSearch %sSearch Limit SettingsSearch Limit...Search a record Search menuSee the modified versionSelectSelect FileSelect a colorSelect a revisionSelect allSelect parentSelect your actionSelect...Select/Activate current rowSelected RecordsSelectionSendSend an e-mail using the recordShortcutsShow active recordsShow bookmarks of filtersShow inactive recordsShow plain textShow revisions...Spell CheckingSubject:SwitchSwitch ViewSwitch viewTemplateTextText EntriesText and IconsThe following action requires to close all tabs. Do you want to continue?The number of decimalThe values of "%s" are not valid.This record has been modified do you want to save it?This record has been modified while you were editing it.To:TodayToggle menuToggle rowToggle selectionToo many requests. Try again later.ToolbarTranslate viewTranslationTrueUnable to check for new version.Unable to complete email entryUnable to set locale %sUnable to set view tree stateUndelete selected record UnderlineUnknownUnmark line for deletionUnselect allUse locale formatUser name:Username:ValueView Logs...View _Logs...WeekWhat is the name of this export?Width:WizardWorking now on the duplicated record(s).Write AnywayYYesYou have to select one record.You must select an import file first.You need to save the record before adding translations.Your selection:_Actions..._Add_Auto-Detect_Cancel_Clear_Close Tab_Copy URL_Delete Export_Delete..._Duplicate_E-Mail..._Export Data..._Import Data..._New_Next_Notes..._Previous_Print..._Relate..._Reload/Undo_Remove_Report..._Save_Save Export_Search_Switch View_URL Exportddevelopment modedisable thread usagego backgo forwardhlogging everything at INFO levelmmodularity, scalability and securitynext yearprevious yearsspecify alternate config filespecify the file used to output logging informationspecify the log level: DEBUG, INFO, WARNING, ERROR, CRITICALspecify the login userspecify the server hostname:portttranslator-creditswyProject-Id-Version: PROJECT VERSION Report-Msgid-Bugs-To: EMAIL@ADDRESS POT-Creation-Date: 2025-04-26 17:11+0200 PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE Last-Translator: FULL NAME Language: ro Language-Team: ro Plural-Forms: nplurals=3; plural=(n==1 ? 0 : (n==0 || (n%100 > 0 && n%100 < 20)) ? 1 : 2); MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: 8bit Generated-By: Babel 2.17.0 "%s" nu este valid conform domeniului său."%s" este obligatoriu.#EROAREînregistrare %d importată.inregistrare %d salvata.înregistrări %d importate.inregistrari %d salvate.%s (%s)%s (model name)%s (string)%s%%%s/Denumire Înregistrare, ,........:Toate câmpurileCâmpurile selectateExporturi predefiniteCreare...Căutare...O nouă versiune este disponibilă!A_tasamente...Despre...AcțiuneAcțiuni...AdăugaAdăugați o notă la înregistrareAdăugați un atașament la înregistrareAdăugare înregistrare existentăAdăugare denumire de câmpAdăugare profil nouAdaugă valoareAdăuga...Aliniere centruAliniere stângaAliniere dreaptaToate fișiereleIgnorați întotdeauna acest avertisment.Eroare de aplicațieComenzi rapide pentru aplicațiiSigur eliminați această înregistrare?Sigur eliminați această înregistrare?Atachment (%s)AtașamenteAtașamente (%s)Atașamente...Bcc:CorpText ingroșatDenumire marcaj:Marcare filtruDe: Export CSV: %sImport CSV: %sParametri CSVC_onectareAnulareIntrerupere al conexiunii la serverul TrytonAnulați salvareaCC:Verificați adresa URL: %sVerificare versiuneAlegere limbăGolireGolire a câmpului ÎnchidereÎnchidere filaReducere toate rândurileReducere rândComparaţieComparați: %sExcepție de concurgențăConectați serverul TrytonCopiereCopiați URL-ul în clipboardCopiază _URL ...Copiere rînduri selectateCopiere text selectatNu s-a putut efectua conexiunea la server.Nu s-a putut găsi o sesiune.Creați o înregistrare nouăCreați o nouă înregistrare Creare relație nouaCreare/Selectie rând nouCreat laCreat la:Creat deCreat de:Tăiere text selectatBază de date:Formatul dateiZiImplicitȘtergereȘtergere înregistrare selectatăȘtergeți înregistrarea selectată Delimitator:Detecție eșuatăCifreFormatul afisareFormatul datei afișatValoarea afișatăDoriți să continuați?Documentație...DescarcaE-Mail...E-mail %sEditareEditare Preferințe utilizatorEditeare înregistrare selectatăEditare...Editat laEditat deModificareComenzi rapide de edițieCodifiere:EroareExtindeți toate rândurileExtindere rânduExpansiune/ReducereExport eşuatFalsFavoriteObținerea listei de baze de dateDenumire CâmpFișier de importat:FişiereCăutareCuloare prim planFormularFormatCautare aproximativaGlobalÎnălţime:AjutorInformații despre gazdă / bază de dateGazdă:IDID:PictogrameIgnorați limita de căutareMarimea imaginiiDimensiunea imaginii este prea mare.ImaginiImport eşuatVersiune incompatibila a serverului.Inserați rânduri copiateCursivMarginiComenzi rapide...Ultima Modificare la:Modificat de:Lansare acțiuneLimităLimită:Rânduri de sarit:LegăturăLista inregistrariScurtaturi Listă/ArboreÎnregistrări enumerateAutentificareJurnale (%s)MGestionare...Selectare rând pentru ștergere/eliminareSelectare linia pentru eliminareModel:LunăMutare cursorMutre în josMutare jos o paginaMutre la stângaMutare dreaptaMutați-vă cel mai josMutare la parinteMută în varfMutare în susMutare sus o paginaDenumireNouUrmătorUrmătoarea înregistrareUrmătoarea inregistrareFila următoareNuNicio acțiune definită.Nu există altă limbă disponibilă.Niciun rezultat găsit.Niciun rezultat găsit.Nimic găsit.Notă (%d/%d)Note (%s)Note...OKDeschidereDeschide filtreDeschidere înregistrări conexeDeschidere relațieDeschidere RaportDeschidere calendarDeschidere înregistrare Deschidere...Deschidere/Căutare relațieOpțiuniTrecere peste definiția '%s'?Modul PDAImagine PNG (* .png)LipireInserare text copiatPre-validarePreferințePreferințePreferințe ...PrevizualizareAnteriorÎnregistrare anterioarăInregistrare anterioarăFila anterioarăTiparireTipărire raportImprimare...ProfilEditor de profilProfil:InchidereCaracter de cotatie:Înregistrare salvată.Înregistrările nu au fost eliminate.Înregistrările au fost eliminate.RelataRelata...Inregistrare relațiiReincarcare/DesfacereEliminare "%s"Eliminare Eliminare FişierȘtergere profil selectatEliminare înregistrare selectatăEliminare marcajRaportRaporteati eroareRaportare...RaportRevizuireRevizuire:SalvareSalvează caSalvare că...Salvați lățimea coloaneiSalvare stare arboreSalvare înregistrareSalvați versiunea curentăCăutareCăutare %sSetări limită de căutareLimita de căutare ...Căutați o înregistrare Meniu CăutareVisualizare a versiunii modificateSelectațiSelectare FişierSelectare culoareSelectare o revizieSelectare totSelectare parinteSelectați acțiuneaSelectare...Selectare/Activare rândul curentÎnregistrări selectateSelecţieTrimiteTrimiteți un e-mail folosind înregistrareaScurtaturiAfișează înregistrări activeAfișați marcajele filtrelorAfișează înregistrări activeAfișare text simpluAfișați revizii...Verificare ortografiaSubiect:SchimbaComutare vedereComutare vedereȘablonTextInregistrari textText și icoaneUrmătoarea acțiune necesită închiderea tuturor filelor. Doriți să continuați?ZecimaleValorile lui "%s" nu sunt valide.Această înregistrare a fost modificată vrei sa o salvezi?Această înregistrare a fost modificată în timp ce o editați.Destinatar:AstăziMeniu ComutareComutare rândComutare selecțiaPrea multe cereri. Încercați mai târziu.Bara de instrumenteTraducere vedereTraducereAdevăratNu se poate verifica dacă există o versiune nouă.Nu se poate finaliza introducerea e-mailuluiImposibil de setat regiunea locală %sImposibil de setat starea arborelui de vizualizareAnulare stergere pentru înregistrarea selectată SubliniereNecunoscutDeselectare rând pentru ștergereDeselectare totUtilizați formatul localUtilizator:Utilizator:ValoareVizualizare Jurnale...Vizualizare_jurnalele ...SăptămânăCare este numele acestui export?Lăţime:AsistentLucrează acum la înregistrările duplicate.Scrie oricumYDaTrebuie selectaa o singura înregistrare.Mai întâi trebuie să selectați un fișier de import.Trebuie să salvați înregistrarea înainte de a adăuga traduceri.Selecția dvs.:_Acțiuni..._Adăuga_Auto detectare_Anulare_Clar_Închideți fila_Copiere URL_Ștergere Export_Ștergere..._Duplicat_E-mail..._Export de date..._Import de date..._NouUrmătorNote..._Anterior_Imprimare..._Relata..._Reincarcare/Desfacere_Elimina_Raportare..._Salvare_Salvare Export_Căutare_Comutare vedere_URL Exportdmod de dezvoltaredezactivare utilizarea thread-urilorîntoarcremergi inaintehlogare totul la nivel INFOmmodularitate, scalabilitate și securitateanul urmatoranul precedentsspecificare alt fisier de configurarespecificare fișier pentru scriere informaţii în jurnalspecificați nivelul jurnalului: DEBUG, INFO, WARNING, ERROR, CRITICALspecificati utilizatorulspecificați numele gazdă server: porttDimitrios Moustoswy././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1703673879.0 tryton-7.0.24/tryton/data/locale/ro/LC_MESSAGES/tryton.po0000644000175000017500000005000214543000027021056 0ustar00cedced# msgid "" msgstr "Content-Type: text/plain; charset=utf-8\n" msgid "specify alternate config file" msgstr "specificare alt fisier de configurare" msgid "development mode" msgstr "mod de dezvoltare" msgid "logging everything at INFO level" msgstr "logare totul la nivel INFO" msgid "specify the log level: DEBUG, INFO, WARNING, ERROR, CRITICAL" msgstr "specificați nivelul jurnalului: DEBUG, INFO, WARNING, ERROR, CRITICAL" msgid "specify the file used to output logging information" msgstr "specificare fișier pentru scriere informaţii în jurnal" msgid "specify the login user" msgstr "specificati utilizatorul" msgid "specify the server hostname:port" msgstr "specificați numele gazdă server: port" msgid "disable thread usage" msgstr "dezactivare utilizarea thread-urilor" #, python-format msgid "Unable to set locale %s" msgstr "Imposibil de setat regiunea locală %s" msgid ", " msgstr ", " msgid ",..." msgstr ",..." #, python-format msgid "%s (%s)" msgstr "%s (%s)" msgid "Select your action" msgstr "Selectați acțiunea" msgid "No action defined." msgstr "Nicio acțiune definită." msgid "By: " msgstr "De: " msgid "Selection" msgstr "Selecţie" msgid "Cancel" msgstr "Anulare" msgid "OK" msgstr "OK" msgid "Your selection:" msgstr "Selecția dvs.:" msgid "Save As..." msgstr "Salvare că..." msgid "Do you want to proceed?" msgstr "Doriți să continuați?" msgid "Always ignore this warning." msgstr "Ignorați întotdeauna acest avertisment." msgid "No" msgstr "Nu" msgid "Yes" msgstr "Da" msgid "Concurrency Exception" msgstr "Excepție de concurgență" msgid "This record has been modified while you were editing it." msgstr "Această înregistrare a fost modificată în timp ce o editați." msgid "Cancel saving" msgstr "Anulați salvarea" msgid "Compare" msgstr "Comparaţie" msgid "See the modified version" msgstr "Visualizare a versiunii modificate" msgid "Write Anyway" msgstr "Scrie oricum" msgid "Save your current version" msgstr "Salvați versiunea curentă" #, python-format msgid "Compare: %s" msgstr "Comparați: %s" msgid "Close" msgstr "Închidere" msgid "Application Error" msgstr "Eroare de aplicație" msgid "Report Bug" msgstr "Raporteati eroare" #, python-format msgid "Check URL: %s" msgstr "Verificați adresa URL: %s" msgid "Unable to check for new version." msgstr "Nu se poate verifica dacă există o versiune nouă." msgid "A new version is available!" msgstr "O nouă versiune este disponibilă!" msgid "Download" msgstr "Descarca" msgid "Could not get a session." msgstr "Nu s-a putut găsi o sesiune." msgid "Too many requests. Try again later." msgstr "Prea multe cereri. Încercați mai târziu." msgid "Not found." msgstr "Nimic găsit." msgid "Not Found." msgstr "Niciun rezultat găsit." msgid "..." msgstr "..." msgid "Search..." msgstr "Căutare..." msgid "Create..." msgstr "Creare..." #, fuzzy, python-format msgid "Create \"%s\"..." msgstr "Creare \"%s\"..." msgid "Value" msgstr "Valoare" msgid "Displayed value" msgstr "Valoarea afișată" msgid "Format" msgstr "Format" msgid "Display format" msgstr "Formatul afisare" msgid "Open the calendar" msgstr "Deschidere calendar" msgid "Date Format" msgstr "Formatul datei" msgid "Displayed date format" msgstr "Formatul datei afișat" msgid "y" msgstr "y" msgid "True" msgstr "Adevărat" msgid "t" msgstr "t" msgid "False" msgstr "Fals" msgid "Digits" msgstr "Cifre" msgid "The number of decimal" msgstr "Zecimale" msgid "Template" msgstr "Șablon" msgid "Edit..." msgstr "Editare..." msgid "View Logs..." msgstr "Vizualizare Jurnale..." msgid "Attachments..." msgstr "Atașamente..." msgid "Notes..." msgstr "Note..." msgid "Actions..." msgstr "Acțiuni..." msgid "Relate..." msgstr "Relata..." msgid "Report..." msgstr "Raportare..." msgid "Print..." msgstr "Imprimare..." msgid "E-Mail..." msgstr "E-Mail..." msgid "Bold" msgstr "Text ingroșat" msgid "Italic" msgstr "Cursiv" msgid "Underline" msgstr "Subliniere" msgid "Align Left" msgstr "Aliniere stânga" msgid "Align Center" msgstr "Aliniere centru" msgid "Align Right" msgstr "Aliniere dreapta" msgid "Justify" msgstr "Margini" msgid "Foreground Color" msgstr "Culoare prim plan" msgid "Select a color" msgstr "Selectare culoare" msgid "Y" msgstr "Y" msgid "M" msgstr "M" msgid "w" msgstr "w" msgid "d" msgstr "d" msgid "h" msgstr "h" msgid "m" msgstr "m" msgid "s" msgstr "s" msgid "Preferences..." msgstr "Preferințe ..." msgid "Toolbar" msgstr "Bara de instrumente" msgid "Default" msgstr "Implicit" msgid "Text and Icons" msgstr "Text și icoane" msgid "Text" msgstr "Text" msgid "Icons" msgstr "Pictograme" msgid "Form" msgstr "Formular" msgid "Save Column Width" msgstr "Salvați lățimea coloanei" msgid "Save Tree State" msgstr "Salvare stare arbore" msgid "Spell Checking" msgstr "Verificare ortografia" msgid "Play Sound for Code Scanner" msgstr "" msgid "PDA Mode" msgstr "Modul PDA" msgid "Search Limit..." msgstr "Limita de căutare ..." msgid "Check Version" msgstr "Verificare versiune" msgid "Options" msgstr "Opțiuni" msgid "Documentation..." msgstr "Documentație..." msgid "Keyboard Shortcuts..." msgstr "Comenzi rapide..." msgid "About..." msgstr "Despre..." msgid "Help" msgstr "Ajutor" msgid "No result found." msgstr "Niciun rezultat găsit." msgid "Favorites" msgstr "Favorite" msgid "Manage..." msgstr "Gestionare..." msgid "Action" msgstr "Acțiune" msgid "" "The following action requires to close all tabs.\n" "Do you want to continue?" msgstr "" "Următoarea acțiune necesită închiderea tuturor filelor.\n" "Doriți să continuați?" msgid "Application Shortcuts" msgstr "Comenzi rapide pentru aplicații" msgid "Global" msgstr "Global" msgid "Preferences" msgstr "Preferințe" msgid "Search menu" msgstr "Meniu Căutare" msgid "Toggle menu" msgstr "Meniu Comutare" msgid "Previous tab" msgstr "Fila anterioară" msgid "Next tab" msgstr "Fila următoare" msgid "Shortcuts" msgstr "Scurtaturi" msgid "Quit" msgstr "Inchidere" msgid "Edition Shortcuts" msgstr "Comenzi rapide de ediție" msgid "Text Entries" msgstr "Inregistrari text" msgid "Cut selected text" msgstr "Tăiere text selectat" msgid "Copy selected text" msgstr "Copiere text selectat" msgid "Paste copied text" msgstr "Inserare text copiat" msgid "Next entry" msgstr "Următoarea inregistrare" msgid "Previous entry" msgstr "Inregistrare anterioară" msgid "Relation Entries" msgstr "Inregistrare relații" msgid "Create new relation" msgstr "Creare relație noua" msgid "Open/Search relation" msgstr "Deschidere/Căutare relație" msgid "List Entries" msgstr "Lista inregistrari" msgid "Switch view" msgstr "Comutare vedere" msgid "Create/Select new line" msgstr "Creare/Selectie rând nou" msgid "Open relation" msgstr "Deschidere relație" msgid "Mark line for deletion/removal" msgstr "Selectare rând pentru ștergere/eliminare" msgid "Mark line for removal" msgstr "Selectare linia pentru eliminare" msgid "Unmark line for deletion" msgstr "Deselectare rând pentru ștergere" msgid "List/Tree Shortcuts" msgstr "Scurtaturi Listă/Arbore" msgid "Move Cursor" msgstr "Mutare cursor" msgid "Move right" msgstr "Mutare dreapta" msgid "Move left" msgstr "Mutre la stânga" msgid "Move up" msgstr "Mutare în sus" msgid "Move down" msgstr "Mutre în jos" msgid "Move up of one page" msgstr "Mutare sus o pagina" msgid "Move down of one page" msgstr "Mutare jos o pagina" msgid "Move to top" msgstr "Mută în varf" msgid "Move to bottom" msgstr "Mutați-vă cel mai jos" msgid "Move to parent" msgstr "Mutare la parinte" msgid "Edition" msgstr "Modificare" msgid "Copy selected rows" msgstr "Copiere rînduri selectate" msgid "Insert copied rows" msgstr "Inserați rânduri copiate" msgid "Select all" msgstr "Selectare tot" msgid "Unselect all" msgstr "Deselectare tot" msgid "Select parent" msgstr "Selectare parinte" msgid "Select/Activate current row" msgstr "Selectare/Activare rândul curent" msgid "Toggle selection" msgstr "Comutare selecția" msgid "Expand/Collapse" msgstr "Expansiune/Reducere" msgid "Expand row" msgstr "Extindere rându" msgid "Collapse row" msgstr "Reducere rând" msgid "Toggle row" msgstr "Comutare rând" msgid "Collapse all rows" msgstr "Reducere toate rândurile" msgid "Expand all rows" msgstr "Extindeți toate rândurile" msgid "Close Tab" msgstr "Închidere fila" msgid "modularity, scalability and security" msgstr "modularitate, scalabilitate și securitate" msgid "translator-credits" msgstr "Dimitrios Moustos" #, python-format msgid "Attachments (%s)" msgstr "Atașamente (%s)" msgid "Code Scanner" msgstr "" msgid "Code" msgstr "" msgid "Profile Editor" msgstr "Editor de profil" msgid "Profile" msgstr "Profil" msgid "Add new profile" msgstr "Adăugare profil nou" msgid "Remove selected profile" msgstr "Ștergere profil selectat" msgid "Host:" msgstr "Gazdă:" msgid "Database:" msgstr "Bază de date:" msgid "Fetching databases list" msgstr "Obținerea listei de baze de date" msgid "Username:" msgstr "Utilizator:" msgid "Incompatible version of the server." msgstr "Versiune incompatibila a serverului." msgid "Could not connect to the server." msgstr "Nu s-a putut efectua conexiunea la server." msgid "Login" msgstr "Autentificare" msgid "_Cancel" msgstr "_Anulare" msgid "Cancel connection to the Tryton server" msgstr "Intrerupere al conexiunii la serverul Tryton" msgid "C_onnect" msgstr "C_onectare" msgid "Connect the Tryton server" msgstr "Conectați serverul Tryton" msgid "Profile:" msgstr "Profil:" msgid "Host / Database information" msgstr "Informații despre gazdă / bază de date" msgid "User name:" msgstr "Utilizator:" msgid "Unable to complete email entry" msgstr "Nu se poate finaliza introducerea e-mailului" #, python-format msgid "E-mail %s" msgstr "E-mail %s" msgid "To:" msgstr "Destinatar:" msgid "Cc:" msgstr "CC:" msgid "Bcc:" msgstr "Bcc:" msgid "Subject:" msgstr "Subiect:" msgid "Body" msgstr "Corp" msgid "Reports" msgstr "Raport" msgid "Attachments" msgstr "Atașamente" msgid "Files" msgstr "Fişiere" msgid "Send" msgstr "Trimite" msgid "Select File" msgstr "Selectare Fişier" msgid "Remove File" msgstr "Eliminare Fişier" msgid "Select" msgstr "Selectați" msgid "Add..." msgstr "Adăuga..." msgid "Preview" msgstr "Previzualizare" msgid "Previous" msgstr "Anterior" msgid "Next" msgstr "Următor" #, python-format msgid "Attachment (%s)" msgstr "Atachment (%s)" #, python-format msgid "Note (%d/%d)" msgstr "Notă (%d/%d)" msgid "You have to select one record." msgstr "Trebuie selectaa o singura înregistrare." msgid "Are you sure to remove this record?" msgstr "Sigur eliminați această înregistrare?" msgid "Are you sure to remove those records?" msgstr "Sigur eliminați această înregistrare?" msgid "Records not removed." msgstr "Înregistrările nu au fost eliminate." msgid "Records removed." msgstr "Înregistrările au fost eliminate." msgid "Working now on the duplicated record(s)." msgstr "Lucrează acum la înregistrările duplicate." msgid "Record saved." msgstr "Înregistrare salvată." msgid "" "This record has been modified\n" "do you want to save it?" msgstr "" "Această înregistrare a fost modificată\n" "vrei sa o salvezi?" msgid "Launch action" msgstr "Lansare acțiune" msgid "Relate" msgstr "Relata" msgid "Open related records" msgstr "Deschidere înregistrări conexe" msgid "Report" msgstr "Raport" msgid "Open report" msgstr "Deschidere Raport" msgid "Print" msgstr "Tiparire" msgid "Print report" msgstr "Tipărire raport" msgid "_Copy URL" msgstr "_Copiere URL" msgid "Copy URL into clipboard" msgstr "Copiați URL-ul în clipboard" msgid "Unknown" msgstr "Necunoscut" msgid "Limit" msgstr "Limită" msgid "Search Limit Settings" msgstr "Setări limită de căutare" msgid "Limit:" msgstr "Limită:" #, python-format msgid "Logs (%s)" msgstr "Jurnale (%s)" msgid "Model:" msgstr "Model:" msgid "ID:" msgstr "ID:" msgid "Created by:" msgstr "Creat de:" msgid "Created at:" msgstr "Creat la:" msgid "Last Modified by:" msgstr "Modificat de:" msgid "Last Modified at:" msgstr "Ultima Modificare la:" #, python-format msgid "Notes (%s)" msgstr "Note (%s)" msgid "Edit User Preferences" msgstr "Editare Preferințe utilizator" msgid "Preference" msgstr "Preferințe" msgid "Revision" msgstr "Revizuire" msgid "Select a revision" msgstr "Selectare o revizie" msgid "Revision:" msgstr "Revizuire:" msgid "_Switch View" msgstr "_Comutare vedere" msgid "Switch View" msgstr "Comutare vedere" msgid "_Previous" msgstr "_Anterior" msgid "Previous Record" msgstr "Înregistrare anterioară" msgid "_Next" msgstr "Următor" msgid "Next Record" msgstr "Următoarea înregistrare" msgid "_Search" msgstr "_Căutare" msgid "_New" msgstr "_Nou" msgid "Create a new record" msgstr "Creați o înregistrare nouă" msgid "_Save" msgstr "_Salvare" msgid "Save this record" msgstr "Salvare înregistrare" msgid "_Reload/Undo" msgstr "_Reincarcare/Desfacere" msgid "Reload/Undo" msgstr "Reincarcare/Desfacere" msgid "_Duplicate" msgstr "_Duplicat" msgid "_Delete..." msgstr "_Ștergere..." msgid "View _Logs..." msgstr "Vizualizare_jurnalele ..." msgid "Show revisions..." msgstr "Afișați revizii..." msgid "A_ttachments..." msgstr "A_tasamente..." msgid "Add an attachment to the record" msgstr "Adăugați un atașament la înregistrare" msgid "_Notes..." msgstr "Note..." msgid "Add a note to the record" msgstr "Adăugați o notă la înregistrare" msgid "_Actions..." msgstr "_Acțiuni..." msgid "_Relate..." msgstr "_Relata..." msgid "_Report..." msgstr "_Raportare..." msgid "_Print..." msgstr "_Imprimare..." msgid "_E-Mail..." msgstr "_E-mail..." msgid "Send an e-mail using the record" msgstr "Trimiteți un e-mail folosind înregistrarea" msgid "_Export Data..." msgstr "_Export de date..." msgid "_Import Data..." msgstr "_Import de date..." msgid "Copy _URL..." msgstr "Copiază _URL ..." msgid "_Close Tab" msgstr "_Închideți fila" msgid "All fields" msgstr "Toate câmpurile" msgid "_Add" msgstr "_Adăuga" msgid "_Remove" msgstr "_Elimina" msgid "_Clear" msgstr "_Clar" msgid "Fields selected" msgstr "Câmpurile selectate" msgid "CSV Parameters" msgstr "Parametri CSV" msgid "Delimiter:" msgstr "Delimitator:" msgid "Quote char:" msgstr "Caracter de cotatie:" msgid "Encoding:" msgstr "Codifiere:" msgid "Use locale format" msgstr "Utilizați formatul local" msgid "Field name" msgstr "Denumire Câmp" #, python-format msgid "CSV Export: %s" msgstr "Export CSV: %s" msgid "_Save Export" msgstr "_Salvare Export" msgid "_URL Export" msgstr "_URL Export" msgid "_Delete Export" msgstr "_Ștergere Export" msgid "Predefined exports" msgstr "Exporturi predefinite" msgid "Name" msgstr "Denumire" msgid "Open" msgstr "Deschidere" msgid "Save" msgstr "Salvare" msgid "Listed Records" msgstr "Înregistrări enumerate" msgid "Selected Records" msgstr "Înregistrări selectate" msgid "Ignore search limit" msgstr "Ignorați limita de căutare" msgid "Add field names" msgstr "Adăugare denumire de câmp" #, python-format msgid "%s (string)" msgstr "%s (string)" #, python-format msgid "%s (model name)" msgstr "%s (model name)" #, python-format msgid "%s/Record Name" msgstr "%s/Denumire Înregistrare" msgid "What is the name of this export?" msgstr "Care este numele acestui export?" #, python-format msgid "Override '%s' definition?" msgstr "Trecere peste definiția '%s'?" #, python-format msgid "%d record saved." msgstr "inregistrare %d salvata." #, python-format msgid "%d records saved." msgstr "inregistrari %d salvate." msgid "Export failed" msgstr "Export eşuat" msgid "Link" msgstr "Legătură" msgid "Delete" msgstr "Ștergere" msgid "Discard changes" msgstr "" msgid "Save and New" msgstr "" #, fuzzy msgid "Add and New" msgstr "Adaugă valoare" msgid "Add" msgstr "Adăuga" msgid "Apply changes" msgstr "" msgid "Switch" msgstr "Schimba" msgid "Remove " msgstr "Eliminare " msgid "Create a new record " msgstr "Creați o nouă înregistrare " msgid "Delete selected record " msgstr "Ștergeți înregistrarea selectată " msgid "Undelete selected record " msgstr "Anulare stergere pentru înregistrarea selectată " #, python-format msgid "CSV Import: %s" msgstr "Import CSV: %s" msgid "_Auto-Detect" msgstr "_Auto detectare" msgid "File to Import:" msgstr "Fișier de importat:" msgid "Open..." msgstr "Deschidere..." msgid "Lines to Skip:" msgstr "Rânduri de sarit:" msgid "You must select an import file first." msgstr "Mai întâi trebuie să selectați un fișier de import." msgid "Detection failed" msgstr "Detecție eșuată" #, python-format msgid "Unknown column header \"%s\"" msgstr "" msgid "Error" msgstr "Eroare" msgid "Import failed" msgstr "Import eşuat" #, python-format msgid "%d record imported." msgstr "înregistrare %d importată." #, python-format msgid "%d records imported." msgstr "înregistrări %d importate." msgid "Search" msgstr "Căutare" msgid "New" msgstr "Nou" #, python-format msgid "Search %s" msgstr "Căutare %s" msgid "Wizard" msgstr "Asistent" msgid "ID" msgstr "ID" msgid "Created by" msgstr "Creat de" msgid "Created at" msgstr "Creat la" msgid "Edited by" msgstr "Editat de" msgid "Edited at" msgstr "Editat la" msgid "Unable to set view tree state" msgstr "Imposibil de setat starea arborelui de vizualizare" #, python-format msgid "\"%s\" is not valid according to its domain." msgstr "\"%s\" nu este valid conform domeniului său." #, python-format msgid "\"%s\" is required." msgstr "\"%s\" este obligatoriu." #, python-format msgid "The values of \"%s\" are not valid." msgstr "Valorile lui \"%s\" nu sunt valide." msgid "Pre-validation" msgstr "Pre-validare" msgid ":" msgstr ":" msgid "Scan" msgstr "" msgid "Image Size" msgstr "Marimea imaginii" msgid "Width:" msgstr "Lăţime:" msgid "Height:" msgstr "Înălţime:" msgid "PNG image (*.png)" msgstr "Imagine PNG (* .png)" msgid "Save As" msgstr "Salvează ca" msgid "Image size too large." msgstr "Dimensiunea imaginii este prea mare." msgid "Copy" msgstr "Copiere" msgid "Paste" msgstr "Lipire" msgid ".." msgstr ".." msgid "Open filters" msgstr "Deschide filtre" msgid "Show bookmarks of filters" msgstr "Afișați marcajele filtrelor" msgid "Remove this bookmark" msgstr "Eliminare marcaj" msgid "Bookmark this filter" msgstr "Marcare filtru" msgid "Show active records" msgstr "Afișează înregistrări active" msgid "Show inactive records" msgstr "Afișează înregistrări active" msgid "Bookmark Name:" msgstr "Denumire marcaj:" msgid "Find" msgstr "Căutare" msgid "Today" msgstr "Astăzi" msgid "go back" msgstr "întoarcre" msgid "go forward" msgstr "mergi inainte" msgid "previous year" msgstr "anul precedent" msgid "next year" msgstr "anul urmator" msgid "Day" msgstr "Zi" msgid "Week" msgstr "Săptămână" msgid "Month" msgstr "Lună" msgid "Select..." msgstr "Selectare..." msgid "Clear" msgstr "Golire" msgid "All files" msgstr "Toate fișierele" msgid "Show plain text" msgstr "Afișare text simplu" msgid "Add value" msgstr "Adaugă valoare" #, python-format msgid "Remove \"%s\"" msgstr "Eliminare \"%s\"" msgid "Images" msgstr "Imagini" msgid "Add existing record" msgstr "Adăugare înregistrare existentă" msgid "Remove selected record" msgstr "Eliminare înregistrare selectată" msgid "Open the record " msgstr "Deschidere înregistrare " msgid "Clear the field " msgstr "Golire a câmpului " msgid "Search a record " msgstr "Căutați o înregistrare " msgid "Edit selected record" msgstr "Editeare înregistrare selectată" msgid "Delete selected record" msgstr "Ștergere înregistrare selectată" #, python-format msgid "%s%%" msgstr "%s%%" msgid "Choose a language" msgstr "Alegere limbă" msgid "Translation" msgstr "Traducere" msgid "Edit" msgstr "Editare" msgid "Fuzzy" msgstr "Cautare aproximativa" msgid "You need to save the record before adding translations." msgstr "Trebuie să salvați înregistrarea înainte de a adăuga traduceri." msgid "No other language available." msgstr "Nu există altă limbă disponibilă." msgid "#ERROR" msgstr "#EROARE" msgid "Translate view" msgstr "Traducere vedere" ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1745680285.1274436 tryton-7.0.24/tryton/data/locale/ru/0000755000175000017500000000000015003173635015413 5ustar00cedced././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1745680285.167444 tryton-7.0.24/tryton/data/locale/ru/LC_MESSAGES/0000755000175000017500000000000015003173635017200 5ustar00cedced././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1745680279.0 tryton-7.0.24/tryton/data/locale/ru/LC_MESSAGES/tryton.mo0000644000175000017500000002333515003173627021103 0ustar00cedced$, - < ? A S m ~       # %= c t     &     . A U n          - 8 H M S [ w }                 '3;Pb t      !6 = HRW _j{  #I/y}     9@ GTV frw          #02C Efh<8";Pdu>6!2JBKK%;O(h5"<3(p6&+$#0Hy2' <(e y 3! %$5Zps##% B OZ ]k r}!.*D(V"6! 6C]m!|,&- T"_ &  6 ;C  ! 6 )!%*! P!Z!q!q! "( "4"C"BT"<""1" # <##]#.# # #"###$$ '$4$R$d$y$$$ $$$ $%!%:% J%X% k%v%%%%0%%U%dF&.&&%s/Record Name..:All fieldsPredefined exportsCreate...Search...A_ttachments...ActionActions...AddAdd an attachment to the recordAdd existing recordAdd valueAll filesAlways ignore this warning.Are you sure to remove this record?Are you sure to remove those records?Attachments (%s)Attachments...Bookmark Name:Bookmark this filterCSV ParametersC_onnectCancel connection to the Tryton serverClearClose TabCompareConcurrency ExceptionConnect the Tryton serverCopy selected textCreate a new recordCreate a new record Create new relationCut selected textDatabase:DeleteDelete selected record Do you want to proceed?E-Mail...EditEdit User PreferencesEncoding:ErrorFalseFetching databases listField nameFile to Import:FindFuzzyHeight:Host / Database informationHost:IDID:Image SizeImagesLaunch actionLimitLimit:Lines to Skip:LinkLoginMModel:NameNewNextNext RecordNo result found.OpenOpen related recordsOpen relationOpen reportOpen...Open/Search relationPNG image (*.png)Paste copied textPreferencePreferencesPreviousPrevious RecordPrintPrint reportPrint...ProfileProfile EditorProfile:RelateRelate...Remove "%s"Remove Remove selected recordRemove this bookmarkReportReport BugReport...SaveSave AsSave As...Save this recordSearchSearch Limit SettingsSearch Limit...Search a record Select your actionSelectionShow bookmarks of filtersShow plain textSpell CheckingSubject:SwitchSwitch viewThe following action requires to close all tabs. Do you want to continue?To:Translate viewTranslationTrueUnable to set locale %sUndelete selected record UnknownUnmark line for deletionUser name:Username:View _Logs...What is the name of this export?Width:WizardWrite AnywayYYour selection:_Actions..._Add_Cancel_Close Tab_Delete..._Duplicate_E-Mail..._Export Data..._Import Data..._New_Next_Previous_Print..._Relate..._Reload/Undo_Remove_Report..._Save_Search_Switch Viewddevelopment modehlogging everything at INFO levelmspecify alternate config filespecify the log level: DEBUG, INFO, WARNING, ERROR, CRITICALspecify the login userwProject-Id-Version: PROJECT VERSION Report-Msgid-Bugs-To: EMAIL@ADDRESS POT-Creation-Date: 2025-04-26 17:11+0200 PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE Last-Translator: FULL NAME Language: ru Language-Team: ru Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2); MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: 8bit Generated-By: Babel 2.17.0 %s/имя записи..:Все поляПредопределенные экспортыСоздать...Поиск...Вложения...ДействиеДействия...ДобавитьДобавить вложение для этой записиДобавить существующую записьДобавить значениеВсе фалыВсегда игнорировать это предупреждение.Вы уверены что хотите удалить эту запись?Вы уверены что хотите удалить эти записи?Вложений (%s)Вложения...Имя закладки:Запомнить этот фильтрПараметры CVSСоединениеОтмена подключения к серверуОчиститьЗакрыть вкладкуСравнитьПрерывание конкурентных записейСоединение с серверомСкопировать выделенный текстСоздать новую записьСоздать новую запись Создать новую связьВырезать выделенный текстБаза данных:УдалитьУдалить выбраную запись Вы хотите продолжить?Эл.почта...РедактироватьИзменить настройки пользователяКодировка:ОшибкаЛожныйПолучение списка баз данныхНаименование поляФайл для импорта:НайтиНеточныйВвысота:Сервер / база данныхСервер:портIDНомер строкиРазмер изображенияИзображенияВыполнить действиеОграничениеКол-во записейСтроки для пропуска:СсылкаЛогинММодель:ИмяНовыйСледующийСледующая записьНичего не найдено.ОткрытьОткрыть связанные записиОткрыть связьОткрыть отчетОткрыть...Открыть / Поиск связейPNG изображение (*.png)Вставить скопированный текстНастройкаНастройкиПредыдущийПредыдущая записьПечатьПечать отчетаПечать...ПрофильРедактор профилейПрофиль:СвязанныеСвязанные...Удалить "%s"Удалить Удалить выбраную записьУдалить эту закладкуОтчетСообщить об ошибкеОтчет...СохранитьСохранить какСохранить как...Сохранить эту записьПоискНастройки ограничения поискаМаксимальное количество строк...Поиск записи Выберите действиеВыборПоказать сохраненные фильтрыПоказать в виде текстаПроверка орфографииТема:ПереключитьПереключить видДанное действие приведет к закрытию всех вкладок. Продолжить?Кому:Перевод текущего окнаПереводИстинныйНе удается установить локализацию %sВосстановить выбраную запись НеизвестноУбрать отметку на удалениеИмя пользователя:Имя пользователя:Просмотр измененийУкажите имя для экспорта.Ширина:МастерЗаписать все равноГВаш выбор:Действия_ДобавитьОтменаЗакрыть вкладкуУдалить...КопироватьЭл.почта...Экспорт данныхИмпорт данныхНовыйСледующаяПредыдущаяпечатьСвязанные...Обновить/Отменить_УдалитьОтчет...СохранитьПоискСменить просмотрдрежим разработкичвсе сообщения на уровне INFOмуказать альтернативный конфигурационный файлуказать уровень вывода сообщений: DEBUG, INFO, WARNING, ERROR, CRITICALуказать имя пользователян././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1703673879.0 tryton-7.0.24/tryton/data/locale/ru/LC_MESSAGES/tryton.po0000644000175000017500000005337214543000027021101 0ustar00cedced# Russian (Russia) translations for tryton. # Copyright (C) 2009 B2CK # This file is distributed under the same license as the tryton project. # Dmitry Klimanov , 2010. msgid "" msgstr "Content-Type: text/plain; charset=utf-8\n" msgid "specify alternate config file" msgstr "указать альтернативный конфигурационный файл" msgid "development mode" msgstr "режим разработки" msgid "logging everything at INFO level" msgstr "все сообщения на уровне INFO" msgid "specify the log level: DEBUG, INFO, WARNING, ERROR, CRITICAL" msgstr "" "указать уровень вывода сообщений: DEBUG, INFO, WARNING, ERROR, CRITICAL" msgid "specify the file used to output logging information" msgstr "" msgid "specify the login user" msgstr "указать имя пользователя" #, fuzzy msgid "specify the server hostname:port" msgstr "укажите имя сервера" msgid "disable thread usage" msgstr "" #, python-format msgid "Unable to set locale %s" msgstr "Не удается установить локализацию %s" #, fuzzy msgid ", " msgstr ", " msgid ",..." msgstr "" #, python-format msgid "%s (%s)" msgstr "" msgid "Select your action" msgstr "Выберите действие" #, fuzzy msgid "No action defined." msgstr "Не определены действия!" msgid "By: " msgstr "" msgid "Selection" msgstr "Выбор" #, fuzzy msgid "Cancel" msgstr "Отмена" msgid "OK" msgstr "" msgid "Your selection:" msgstr "Ваш выбор:" msgid "Save As..." msgstr "Сохранить как..." msgid "Do you want to proceed?" msgstr "Вы хотите продолжить?" msgid "Always ignore this warning." msgstr "Всегда игнорировать это предупреждение." msgid "No" msgstr "" msgid "Yes" msgstr "" msgid "Concurrency Exception" msgstr "Прерывание конкурентных записей" msgid "This record has been modified while you were editing it." msgstr "" msgid "Cancel saving" msgstr "" msgid "Compare" msgstr "Сравнить" msgid "See the modified version" msgstr "" msgid "Write Anyway" msgstr "Записать все равно" msgid "Save your current version" msgstr "" #, fuzzy, python-format msgid "Compare: %s" msgstr "Сравнить: %s" msgid "Close" msgstr "" #, fuzzy msgid "Application Error" msgstr "Ошибка приложения!" msgid "Report Bug" msgstr "Сообщить об ошибке" #, python-format msgid "Check URL: %s" msgstr "" msgid "Unable to check for new version." msgstr "" msgid "A new version is available!" msgstr "" msgid "Download" msgstr "" #, fuzzy msgid "Could not get a session." msgstr "Не удается подключиться к серверу!" msgid "Too many requests. Try again later." msgstr "" #, fuzzy msgid "Not found." msgstr "Ничего не найдено." #, fuzzy msgid "Not Found." msgstr "Ничего не найдено." #, fuzzy msgid "..." msgstr "..." msgid "Search..." msgstr "Поиск..." msgid "Create..." msgstr "Создать..." #, fuzzy, python-format msgid "Create \"%s\"..." msgstr "Создать \"%s\"..." #, fuzzy msgid "Value" msgstr "Добавить значение" #, fuzzy msgid "Displayed value" msgstr "Добавить значение" #, fuzzy msgid "Format" msgstr "Нормальный" msgid "Display format" msgstr "" #, fuzzy msgid "Open the calendar" msgstr "Открыть календарь " msgid "Date Format" msgstr "" msgid "Displayed date format" msgstr "" msgid "y" msgstr "" msgid "True" msgstr "Истинный" msgid "t" msgstr "" msgid "False" msgstr "Ложный" msgid "Digits" msgstr "" msgid "The number of decimal" msgstr "" #, fuzzy msgid "Template" msgstr "Связанные" #, fuzzy msgid "Edit..." msgstr "_Выход..." #, fuzzy msgid "View Logs..." msgstr "Просмотр изменений" msgid "Attachments..." msgstr "Вложения..." msgid "Notes..." msgstr "" msgid "Actions..." msgstr "Действия..." msgid "Relate..." msgstr "Связанные..." msgid "Report..." msgstr "Отчет..." msgid "Print..." msgstr "Печать..." msgid "E-Mail..." msgstr "Эл.почта..." msgid "Bold" msgstr "" msgid "Italic" msgstr "" msgid "Underline" msgstr "" msgid "Align Left" msgstr "" msgid "Align Center" msgstr "" msgid "Align Right" msgstr "" msgid "Justify" msgstr "" msgid "Foreground Color" msgstr "" msgid "Select a color" msgstr "" msgid "Y" msgstr "Г" msgid "M" msgstr "М" msgid "w" msgstr "н" msgid "d" msgstr "д" msgid "h" msgstr "ч" msgid "m" msgstr "м" msgid "s" msgstr "" #, fuzzy msgid "Preferences..." msgstr "Настройки..." #, fuzzy msgid "Toolbar" msgstr "Панель инструментов" #, fuzzy msgid "Default" msgstr "По умолчанию" #, fuzzy msgid "Text and Icons" msgstr "Текст и Значки" #, fuzzy msgid "Text" msgstr "Текст" #, fuzzy msgid "Icons" msgstr "Значки" #, fuzzy msgid "Form" msgstr "Форма" msgid "Save Column Width" msgstr "" #, fuzzy msgid "Save Tree State" msgstr "Запомнить состояние дерева" msgid "Spell Checking" msgstr "Проверка орфографии" msgid "Play Sound for Code Scanner" msgstr "" msgid "PDA Mode" msgstr "" msgid "Search Limit..." msgstr "Максимальное количество строк..." msgid "Check Version" msgstr "" #, fuzzy msgid "Options" msgstr "_Параметры" #, fuzzy msgid "Documentation..." msgstr "Действия..." #, fuzzy msgid "Keyboard Shortcuts..." msgstr "_Быстрые клавиши" #, fuzzy msgid "About..." msgstr "О программе" #, fuzzy msgid "Help" msgstr "Помощь" msgid "No result found." msgstr "Ничего не найдено." #, fuzzy msgid "Favorites" msgstr "Избранное" msgid "Manage..." msgstr "" msgid "Action" msgstr "Действие" msgid "" "The following action requires to close all tabs.\n" "Do you want to continue?" msgstr "" "Данное действие приведет к закрытию всех вкладок.\n" "Продолжить?" msgid "Application Shortcuts" msgstr "" msgid "Global" msgstr "" msgid "Preferences" msgstr "Настройки" msgid "Search menu" msgstr "" msgid "Toggle menu" msgstr "" #, fuzzy msgid "Previous tab" msgstr "Предыдущая вкладка" #, fuzzy msgid "Next tab" msgstr "Следующая вкладка" msgid "Shortcuts" msgstr "" msgid "Quit" msgstr "" msgid "Edition Shortcuts" msgstr "" msgid "Text Entries" msgstr "" msgid "Cut selected text" msgstr "Вырезать выделенный текст" msgid "Copy selected text" msgstr "Скопировать выделенный текст" msgid "Paste copied text" msgstr "Вставить скопированный текст" msgid "Next entry" msgstr "" msgid "Previous entry" msgstr "" msgid "Relation Entries" msgstr "" msgid "Create new relation" msgstr "Создать новую связь" msgid "Open/Search relation" msgstr "Открыть / Поиск связей" msgid "List Entries" msgstr "" msgid "Switch view" msgstr "Переключить вид" msgid "Create/Select new line" msgstr "" msgid "Open relation" msgstr "Открыть связь" msgid "Mark line for deletion/removal" msgstr "" msgid "Mark line for removal" msgstr "" msgid "Unmark line for deletion" msgstr "Убрать отметку на удаление" msgid "List/Tree Shortcuts" msgstr "" msgid "Move Cursor" msgstr "" msgid "Move right" msgstr "" msgid "Move left" msgstr "" msgid "Move up" msgstr "" msgid "Move down" msgstr "" msgid "Move up of one page" msgstr "" msgid "Move down of one page" msgstr "" msgid "Move to top" msgstr "" msgid "Move to bottom" msgstr "" msgid "Move to parent" msgstr "" #, fuzzy msgid "Edition" msgstr "Редактировать" #, fuzzy msgid "Copy selected rows" msgstr "Скопировать выделенный текст" msgid "Insert copied rows" msgstr "" msgid "Select all" msgstr "" msgid "Unselect all" msgstr "" msgid "Select parent" msgstr "" msgid "Select/Activate current row" msgstr "" msgid "Toggle selection" msgstr "" msgid "Expand/Collapse" msgstr "" msgid "Expand row" msgstr "" msgid "Collapse row" msgstr "" msgid "Toggle row" msgstr "" msgid "Collapse all rows" msgstr "" msgid "Expand all rows" msgstr "" msgid "Close Tab" msgstr "Закрыть вкладку" msgid "modularity, scalability and security" msgstr "" msgid "translator-credits" msgstr "" #, python-format msgid "Attachments (%s)" msgstr "Вложений (%s)" msgid "Code Scanner" msgstr "" msgid "Code" msgstr "" msgid "Profile Editor" msgstr "Редактор профилей" msgid "Profile" msgstr "Профиль" msgid "Add new profile" msgstr "" msgid "Remove selected profile" msgstr "" msgid "Host:" msgstr "Сервер:порт" msgid "Database:" msgstr "База данных:" msgid "Fetching databases list" msgstr "Получение списка баз данных" msgid "Username:" msgstr "Имя пользователя:" #, fuzzy msgid "Incompatible version of the server." msgstr "Несовместимая версия сервера" #, fuzzy msgid "Could not connect to the server." msgstr "Не удается подключиться к серверу!" msgid "Login" msgstr "Логин" msgid "_Cancel" msgstr "Отмена" msgid "Cancel connection to the Tryton server" msgstr "Отмена подключения к серверу" msgid "C_onnect" msgstr "Соединение" msgid "Connect the Tryton server" msgstr "Соединение с сервером" msgid "Profile:" msgstr "Профиль:" msgid "Host / Database information" msgstr "Сервер / база данных" msgid "User name:" msgstr "Имя пользователя:" #, fuzzy msgid "Unable to complete email entry" msgstr "Не удается установить локализацию %s" #, fuzzy, python-format msgid "E-mail %s" msgstr "Эл.почта %s" msgid "To:" msgstr "Кому:" #, fuzzy msgid "Cc:" msgstr "Скрытая копия:" msgid "Bcc:" msgstr "" msgid "Subject:" msgstr "Тема:" #, fuzzy msgid "Body" msgstr "Сообщение:" #, fuzzy msgid "Reports" msgstr "Отчет" #, fuzzy msgid "Attachments" msgstr "Вложение:" msgid "Files" msgstr "" msgid "Send" msgstr "" #, fuzzy msgid "Select File" msgstr "Выбор" #, fuzzy msgid "Remove File" msgstr "Удалить " msgid "Select" msgstr "" msgid "Add..." msgstr "" #, fuzzy msgid "Preview" msgstr "Предыдущий" msgid "Previous" msgstr "Предыдущий" msgid "Next" msgstr "Следующий" #, fuzzy, python-format msgid "Attachment (%s)" msgstr "Вложений (%s)" #, python-format msgid "Note (%d/%d)" msgstr "" #, fuzzy msgid "You have to select one record." msgstr "Вы должны выбрать одну запись!" msgid "Are you sure to remove this record?" msgstr "Вы уверены что хотите удалить эту запись?" msgid "Are you sure to remove those records?" msgstr "Вы уверены что хотите удалить эти записи?" #, fuzzy msgid "Records not removed." msgstr "Записи не удалены!" #, fuzzy msgid "Records removed." msgstr "Записи удалены!" #, fuzzy msgid "Working now on the duplicated record(s)." msgstr "Сейчас работает на дублирующихся записях!" #, fuzzy msgid "Record saved." msgstr "Запись сохранена!" #, fuzzy msgid "" "This record has been modified\n" "do you want to save it?" msgstr "" "Эта запись была изменена\n" "Вы хотите её сохранить?" msgid "Launch action" msgstr "Выполнить действие" msgid "Relate" msgstr "Связанные" msgid "Open related records" msgstr "Открыть связанные записи" msgid "Report" msgstr "Отчет" msgid "Open report" msgstr "Открыть отчет" msgid "Print" msgstr "Печать" msgid "Print report" msgstr "Печать отчета" msgid "_Copy URL" msgstr "" msgid "Copy URL into clipboard" msgstr "" msgid "Unknown" msgstr "Неизвестно" msgid "Limit" msgstr "Ограничение" msgid "Search Limit Settings" msgstr "Настройки ограничения поиска" msgid "Limit:" msgstr "Кол-во записей" #, python-format msgid "Logs (%s)" msgstr "" msgid "Model:" msgstr "Модель:" msgid "ID:" msgstr "Номер строки" #, fuzzy msgid "Created by:" msgstr "Дата создания" #, fuzzy msgid "Created at:" msgstr "Дата создания" #, fuzzy msgid "Last Modified by:" msgstr "Изменена пользователем:" #, fuzzy msgid "Last Modified at:" msgstr "Дата изменения:" #, python-format msgid "Notes (%s)" msgstr "" msgid "Edit User Preferences" msgstr "Изменить настройки пользователя" msgid "Preference" msgstr "Настройка" #, fuzzy msgid "Revision" msgstr "Предыдущий" #, fuzzy msgid "Select a revision" msgstr "Выбор" #, fuzzy msgid "Revision:" msgstr "Предыдущий" msgid "_Switch View" msgstr "Сменить просмотр" #, fuzzy msgid "Switch View" msgstr "Сменить просмотр" msgid "_Previous" msgstr "Предыдущая" msgid "Previous Record" msgstr "Предыдущая запись" msgid "_Next" msgstr "Следующая" msgid "Next Record" msgstr "Следующая запись" msgid "_Search" msgstr "Поиск" msgid "_New" msgstr "Новый" msgid "Create a new record" msgstr "Создать новую запись" msgid "_Save" msgstr "Сохранить" msgid "Save this record" msgstr "Сохранить эту запись" msgid "_Reload/Undo" msgstr "Обновить/Отменить" #, fuzzy msgid "Reload/Undo" msgstr "Обновить/Отменить" msgid "_Duplicate" msgstr "Копировать" msgid "_Delete..." msgstr "Удалить..." msgid "View _Logs..." msgstr "Просмотр изменений" msgid "Show revisions..." msgstr "" msgid "A_ttachments..." msgstr "Вложения..." msgid "Add an attachment to the record" msgstr "Добавить вложение для этой записи" msgid "_Notes..." msgstr "" msgid "Add a note to the record" msgstr "" msgid "_Actions..." msgstr "Действия" msgid "_Relate..." msgstr "Связанные..." msgid "_Report..." msgstr "Отчет..." msgid "_Print..." msgstr "печать" msgid "_E-Mail..." msgstr "Эл.почта..." #, fuzzy msgid "Send an e-mail using the record" msgstr "Добавить существующую запись" msgid "_Export Data..." msgstr "Экспорт данных" msgid "_Import Data..." msgstr "Импорт данных" msgid "Copy _URL..." msgstr "" msgid "_Close Tab" msgstr "Закрыть вкладку" msgid "All fields" msgstr "Все поля" msgid "_Add" msgstr "_Добавить" msgid "_Remove" msgstr "_Удалить" #, fuzzy msgid "_Clear" msgstr "Очистить" msgid "Fields selected" msgstr "" msgid "CSV Parameters" msgstr "Параметры CVS" msgid "Delimiter:" msgstr "" msgid "Quote char:" msgstr "" msgid "Encoding:" msgstr "Кодировка:" msgid "Use locale format" msgstr "" msgid "Field name" msgstr "Наименование поля" #, python-format msgid "CSV Export: %s" msgstr "" #, fuzzy msgid "_Save Export" msgstr "Сохранить настройку экспорта" #, fuzzy msgid "_URL Export" msgstr "Сохранить настройку экспорта" #, fuzzy msgid "_Delete Export" msgstr "Удалить настройку экспорта" msgid "Predefined exports" msgstr "Предопределенные экспорты" msgid "Name" msgstr "Имя" msgid "Open" msgstr "Открыть" msgid "Save" msgstr "Сохранить" #, fuzzy msgid "Listed Records" msgstr "Изменить выбранную запись " #, fuzzy msgid "Selected Records" msgstr "Изменить выбранную запись " msgid "Ignore search limit" msgstr "" #, fuzzy msgid "Add field names" msgstr "Добавить имя поля" #, python-format msgid "%s (string)" msgstr "" #, python-format msgid "%s (model name)" msgstr "" #, python-format msgid "%s/Record Name" msgstr "%s/имя записи" msgid "What is the name of this export?" msgstr "Укажите имя для экспорта." #, python-format msgid "Override '%s' definition?" msgstr "" #, fuzzy, python-format msgid "%d record saved." msgstr "%d запись сохранена!" #, fuzzy, python-format msgid "%d records saved." msgstr "%d записей сохранено!" msgid "Export failed" msgstr "" msgid "Link" msgstr "Ссылка" msgid "Delete" msgstr "Удалить" msgid "Discard changes" msgstr "" msgid "Save and New" msgstr "" #, fuzzy msgid "Add and New" msgstr "Добавить значение" msgid "Add" msgstr "Добавить" msgid "Apply changes" msgstr "" msgid "Switch" msgstr "Переключить" msgid "Remove " msgstr "Удалить " msgid "Create a new record " msgstr "Создать новую запись " msgid "Delete selected record " msgstr "Удалить выбраную запись " msgid "Undelete selected record " msgstr "Восстановить выбраную запись " #, python-format msgid "CSV Import: %s" msgstr "" #, fuzzy msgid "_Auto-Detect" msgstr "Авто-обнаружение" msgid "File to Import:" msgstr "Файл для импорта:" msgid "Open..." msgstr "Открыть..." msgid "Lines to Skip:" msgstr "Строки для пропуска:" #, fuzzy msgid "You must select an import file first." msgstr "Вы должны сначала выбрать файл для импорта!" msgid "Detection failed" msgstr "" #, python-format msgid "Unknown column header \"%s\"" msgstr "" msgid "Error" msgstr "Ошибка" msgid "Import failed" msgstr "" #, fuzzy, python-format msgid "%d record imported." msgstr "%d запись импортирована!" #, fuzzy, python-format msgid "%d records imported." msgstr "%d записей импортировано!" msgid "Search" msgstr "Поиск" msgid "New" msgstr "Новый" #, python-format msgid "Search %s" msgstr "" msgid "Wizard" msgstr "Мастер" msgid "ID" msgstr "ID" #, fuzzy msgid "Created by" msgstr "Дата создания" #, fuzzy msgid "Created at" msgstr "Дата создания" #, fuzzy msgid "Edited by" msgstr "Редактировать" #, fuzzy msgid "Edited at" msgstr "Редактировать" #, fuzzy msgid "Unable to set view tree state" msgstr "Не удается установить локализацию %s" #, python-format msgid "\"%s\" is not valid according to its domain." msgstr "" #, python-format msgid "\"%s\" is required." msgstr "" #, python-format msgid "The values of \"%s\" are not valid." msgstr "" msgid "Pre-validation" msgstr "" msgid ":" msgstr ":" msgid "Scan" msgstr "" msgid "Image Size" msgstr "Размер изображения" msgid "Width:" msgstr "Ширина:" msgid "Height:" msgstr "Ввысота:" msgid "PNG image (*.png)" msgstr "PNG изображение (*.png)" msgid "Save As" msgstr "Сохранить как" #, fuzzy msgid "Image size too large." msgstr "Размер изображения слишком большой!" msgid "Copy" msgstr "" msgid "Paste" msgstr "" msgid ".." msgstr ".." msgid "Open filters" msgstr "" msgid "Show bookmarks of filters" msgstr "Показать сохраненные фильтры" msgid "Remove this bookmark" msgstr "Удалить эту закладку" msgid "Bookmark this filter" msgstr "Запомнить этот фильтр" msgid "Show active records" msgstr "" msgid "Show inactive records" msgstr "" msgid "Bookmark Name:" msgstr "Имя закладки:" msgid "Find" msgstr "Найти" #, fuzzy msgid "Today" msgstr "Сообщение:" msgid "go back" msgstr "" msgid "go forward" msgstr "" msgid "previous year" msgstr "" msgid "next year" msgstr "" msgid "Day" msgstr "" msgid "Week" msgstr "" #, fuzzy msgid "Month" msgstr "Переключить вид" msgid "Select..." msgstr "" msgid "Clear" msgstr "Очистить" msgid "All files" msgstr "Все фалы" msgid "Show plain text" msgstr "Показать в виде текста" msgid "Add value" msgstr "Добавить значение" #, python-format msgid "Remove \"%s\"" msgstr "Удалить \"%s\"" msgid "Images" msgstr "Изображения" msgid "Add existing record" msgstr "Добавить существующую запись" msgid "Remove selected record" msgstr "Удалить выбраную запись" #, fuzzy msgid "Open the record " msgstr "Открыть запись " msgid "Clear the field " msgstr "" msgid "Search a record " msgstr "Поиск записи " #, fuzzy msgid "Edit selected record" msgstr "Изменить выбранную запись " #, fuzzy msgid "Delete selected record" msgstr "Удалить выбраную запись" #, python-format msgid "%s%%" msgstr "" msgid "Choose a language" msgstr "" msgid "Translation" msgstr "Перевод" msgid "Edit" msgstr "Редактировать" msgid "Fuzzy" msgstr "Неточный" #, fuzzy msgid "You need to save the record before adding translations." msgstr "Необходимо сохранить запись перед добавлением перевода!" #, fuzzy msgid "No other language available." msgstr "Отсутствуют другие доступные языки!" msgid "#ERROR" msgstr "" msgid "Translate view" msgstr "Перевод текущего окна" ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1745680285.1274436 tryton-7.0.24/tryton/data/locale/sl/0000755000175000017500000000000015003173635015403 5ustar00cedced././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1745680285.167444 tryton-7.0.24/tryton/data/locale/sl/LC_MESSAGES/0000755000175000017500000000000015003173635017170 5ustar00cedced././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1745680279.0 tryton-7.0.24/tryton/data/locale/sl/LC_MESSAGES/tryton.mo0000644000175000017500000004500215003173627021066 0ustar00cedcedL |*}  %16EHMPTVh  ?K_o      #%4Z jv&  2@ D R`rx     0=P c    &2 D NZ^fm  !2 ; EOTj        & 1 A G L ] b i o v ~         #  !3!:!B!X!j! |!!!!! !!!! !! !!","3" 9" E"O" e" o"z"" """""" " """"#!# 2# =# H# U#`#i#l# q#~# # #######$$.$4$F$b$ q$ |$$$$$$ $$ $$$$%% % !%/%D%U% \%f% w% % % %%%%% % %&& &#&(& 0&;&M& ]&j&{&&& &&&& && ' ''-' ?' J'X' k'u'' ''' ''' ((/(A(P(Y( `( l(x(( ((I((!)5$)8Z))) ) ))#))) * * *2*Q*i** **** ** + + + &+ 3+A+ F+g+n+(u+ ++++%+7+., >,J, O,\,d, k, v,, , , ,,,,, , , , , -- -$- *-7- ?- L-X-Z-k-- -- --$- - ---3.<I.. ......-00000 1#1:1B1R1[1`1p1s1x1{1111111%1 2 2$2 *252;2U2j222222222 33,3>3W3#i3$3 33 3 33 33 344 4 +4 84F4 O4'Y44444 4444 44 55%5 45 >5L5h555555%5 6(686J6a6u6 666 666 66667767?7]7m7~777777 7 788&8 98 C8N8U8^8 o8z888888 8$8 899"9(979?9F9N9 W9a9h9 99999999 9$9:,:5:M:i:: :: ::::%:;; !;/; 1;&>;e;;;; ;; ; ;;;; <<4<:< ?<I<Y<i<{<~<<< < << < = == =&=<=N= ^=l=}== =!=====>$> 4> ?> J> X> b>l>|>>>> >>>>> >>??+?0?9?I? d?r????? ?? ? @ @ @(@ /@ =@K@c@y@@@@@ @@@AA"AAAHA XAeA wAA A A!AAAA!A BB4BNBhBB BBBBBBBBBGCOC%hC0C)CCCCDD+4D`DoD~DD?D1D+D"&EIE cEnEvE E EE EEEFF)F/FIF RF'\FFFFF(F,F G G"G )G 6G AGKG ZGgG wGGGGGG G G G GGH H *H8H @HNHVH gHrHtHH H HHHH&HII I,"IOI>hII.II IIJ"%s" is not valid according to its domain."%s" is required.#ERROR%d record imported.%d record saved.%d records imported.%d records saved.%s (%s)%s (model name)%s (string)%s%%%s/Record Name, ,........:All fieldsFields selectedPredefined exportsCreate...Search...A new version is available!A_ttachments...About...ActionActions...AddAdd a note to the recordAdd an attachment to the recordAdd and NewAdd existing recordAdd field namesAdd new profileAdd valueAdd...Align CenterAlign LeftAlign RightAll filesAlways ignore this warning.Application ErrorApplication ShortcutsApply changesAre you sure to remove this record?Are you sure to remove those records?Attachment (%s)AttachmentsAttachments (%s)Attachments...Bcc:BodyBoldBookmark Name:Bookmark this filterBy: CSV Export: %sCSV Import: %sCSV ParametersC_onnectCancelCancel connection to the Tryton serverCancel savingCc:Check URL: %sCheck VersionChoose a languageClearClear the field CloseClose TabCodeCode ScannerCollapse all rowsCollapse rowCompareCompare: %sConcurrency ExceptionConnect the Tryton serverCopyCopy URL into clipboardCopy _URL...Copy selected rowsCopy selected textCould not connect to the server.Could not get a session.Create "%s"...Create a new recordCreate a new record Create new relationCreate/Select new lineCreated atCreated at:Created byCreated by:Cut selected textDatabase:Date FormatDayDefaultDeleteDelete selected recordDelete selected record Delimiter:Detection failedDigitsDiscard changesDisplay formatDisplayed date formatDisplayed valueDo you want to proceed?Documentation...DownloadE-Mail...E-mail %sEditEdit User PreferencesEdit selected recordEdit...Edited atEdited byEditionEdition ShortcutsEncoding:ErrorExpand all rowsExpand rowExpand/CollapseExport failedFalseFavoritesFetching databases listField nameFile to Import:FilesFindForeground ColorFormFormatFuzzyGlobalHeight:HelpHost / Database informationHost:IDID:IconsIgnore search limitImage SizeImage size too large.ImagesImport failedIncompatible version of the server.Insert copied rowsItalicJustifyKeyboard Shortcuts...Last Modified at:Last Modified by:Launch actionLimitLimit:Lines to Skip:LinkList EntriesList/Tree ShortcutsListed RecordsLoginLogs (%s)MManage...Mark line for deletion/removalMark line for removalModel:MonthMove CursorMove downMove down of one pageMove leftMove rightMove to bottomMove to parentMove to topMove upMove up of one pageNameNewNextNext RecordNext entryNext tabNoNo action defined.No other language available.No result found.Not Found.Not found.Note (%d/%d)Notes (%s)Notes...OKOpenOpen filtersOpen related recordsOpen relationOpen reportOpen the calendarOpen the record Open...Open/Search relationOptionsOverride '%s' definition?PDA ModePNG image (*.png)PastePaste copied textPlay Sound for Code ScannerPre-validationPreferencePreferencesPreferences...PreviewPreviousPrevious RecordPrevious entryPrevious tabPrintPrint reportPrint...ProfileProfile EditorProfile:QuitQuote char:Record saved.Records not removed.Records removed.RelateRelate...Relation EntriesReload/UndoRemove "%s"Remove Remove FileRemove selected profileRemove selected recordRemove this bookmarkReportReport BugReport...ReportsRevisionRevision:SaveSave AsSave As...Save Column WidthSave Tree StateSave and NewSave this recordSave your current versionScanSearchSearch %sSearch Limit SettingsSearch Limit...Search a record Search menuSee the modified versionSelectSelect FileSelect a colorSelect a revisionSelect allSelect parentSelect your actionSelect...Select/Activate current rowSelected RecordsSelectionSendSend an e-mail using the recordShortcutsShow active recordsShow bookmarks of filtersShow inactive recordsShow plain textShow revisions...Spell CheckingSubject:SwitchSwitch ViewSwitch viewTemplateTextText EntriesText and IconsThe following action requires to close all tabs. Do you want to continue?The number of decimalThe values of "%s" are not valid.This record has been modified do you want to save it?This record has been modified while you were editing it.To:TodayToggle menuToggle rowToggle selectionToo many requests. Try again later.ToolbarTranslate viewTranslationTrueUnable to check for new version.Unable to complete email entryUnable to set locale %sUnable to set view tree stateUndelete selected record UnderlineUnknownUnknown column header "%s"Unmark line for deletionUnselect allUse locale formatUser name:Username:ValueView Logs...View _Logs...WeekWhat is the name of this export?Width:WizardWorking now on the duplicated record(s).Write AnywayYYesYou have to select one record.You must select an import file first.You need to save the record before adding translations.Your selection:_Actions..._Add_Auto-Detect_Cancel_Clear_Close Tab_Copy URL_Delete Export_Delete..._Duplicate_E-Mail..._Export Data..._Import Data..._New_Next_Notes..._Previous_Print..._Relate..._Reload/Undo_Remove_Report..._Save_Save Export_Search_Switch View_URL Exportddevelopment modedisable thread usagego backgo forwardhlogging everything at INFO levelmmodularity, scalability and securitynext yearprevious yearsspecify alternate config filespecify the file used to output logging informationspecify the log level: DEBUG, INFO, WARNING, ERROR, CRITICALspecify the login userspecify the server hostname:portttranslator-creditswyProject-Id-Version: PROJECT VERSION Report-Msgid-Bugs-To: EMAIL@ADDRESS POT-Creation-Date: 2025-04-26 17:11+0200 PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE Last-Translator: FULL NAME Language: sl Language-Team: sl Plural-Forms: nplurals=4; plural=(n%100==1 ? 0 : n%100==2 ? 1 : n%100==3 || n%100==4 ? 2 : 3); MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: 8bit Generated-By: Babel 2.17.0 Polje "%s" ni veljavno glede na svojo domeno.Polje "%s" je obvezno.#NAPAKA%d zapis uvožen.%d zapis shranjen.%d zapisov uvoženih.%d zapisov shranjenih.%s (%s)%s (ime modela)%s (niz)%s%%%s/Naziv zapisa, ,........:Vsa poljaIzbrana poljaPredefinirani izvoziUstvari...Išči...Nova različica programa je na voljo!P_riponke...Vizitka...Ukrep_Ukrepi...DodajK zapisu dodaj zabeležkoDodaj prilogo zapisuDodaj in ustvari novoDodaj obstoječi zapisDodaj imena poljDodaj nov profilDodaj vrednostDodaj...Sredinska poravnavaLeva poravnavaDesna poravnavaVse datotekeVedno prezri to opozorilo.Programska napakaBližnjice na tipkovniciUporabi spremembeAli res želiš izbrisati ta zapis?Ali res želiš izbrisati te zapise?Priloga (%s)PrilogePriloge (%s)Priloge...Bcc:Telo besedilaKrepkoIme zaznamka:Shrani ta filterOd: CSV izvoz: %sCSV uvoz: %sCSV parametriP_ovežiPrekličiPrekliči povezavo do Tryton strežnikaPrekliči shranjevanjeCc:Preveri URL: %sPreveri posodobitveIzberi jezikPočistiPočisti polje ZapriZapri zavihekKodaČitalnik kodSkrči vse vrsticeSkrči vrsticoPrimerjajPrimerjaj: %sIzjema sočasnega izvajanjaPoveži se s Tryton strežnikomKopirajKopiraj URL na odložišče_Kopiraj URL ...Kopiraj izbrane vrsticeKopiraj vsebinoNi se mogoče povezati s strežnikom.Ni mogoče vzpostaviti seje.Ustvari "%s"...Ustvari nov zapisUstvari nov zapis Dodajanje nove vezeUstvari/Izberi novo vrsticoUstvarjeno obUstvarjeno ob:UstvarilUstvaril:Izreži vsebinoPodatkovna baza:Oblika datumaDanPrivzetoZbrišiIzbriši izbran zapisZbriši izbran zapis Ločilo:Zaznavanje datoteke ni uspeloDecimalna mestaZavrzi spremembeOblika prikazaPrikazana oblika datumaPrikazana vrednostAli želite nadaljevati?Dokumentacija...PrenesiE-pošta...E-pošta %sUrediUredi uporabniške nastavitveUredi izbran zapisUredi ...Urejeno obUredilUrejanjeUredi bližnjiceKodiranje:NapakaRazširi vse vrsticeRazširi vrsticoRazširi/SkrčiIzvoz ni uspelNePriljubljenePridobivanje seznama podatkovnih bazNaziv poljaDatoteka za uvoz:DatotekeNajdiBarva ospredjaObrazecOblikaNejasnoGlobalnoVelikost:PomočStrežnik in podatkovna bazaStrežnik:IDID:IkonePrezri omejitev iskanjaVelikost podobePodoba je prevelika.PodobeUvoz ni uspelNezdružljiva različica strežnika.Vstavi kopirane vrsticePoševnoObojestranska poravnavaBližnjice na tipkovnici...Nazadnje spremenjeno:Nazadnje spremenil:Zaženi ukrepOmejitevOmejitev:Izpuščene vrstice:PovezavaPolja s seznamiBližnjice seznama/drevesnega pogledaPrikazani zapisiPrijavaDnevniki (%s)MUpravljaj...Izberi vrstico za brisanje/odstranitevOznači vrstico za odstranitevModel:MesecPremikiPremik dolPremik za eno stran dolPremik levoPremik desnoPremik na konecPremik na nadrejeno vejoPremik na začetekPremik gorPremik za eno stran gorNazivNovoNaslednjiNaslednji zapisNaslednje poljeNaslednji zavihekNeNobenega ukrepa ni določenega.Drugih jezikov ni na voljo.Brez rezultatov.Ni najdeno.Ni najdeno.Zapisek (%d/%d)Zapiski (%s)Zapiski ...V reduOdpriOdpri filtreOdpri povezane zapiseUrejenje postavkeOdpri poročiloOdpri koledarOdpri zapis Odpri...Urejanje/iskanje vezeMožnostiPrepišem definicijo izvoza '%s'?Tablični načinPNG podoba (*.png)PrilepiPrilepi vsebinoPredvajaj zvok za čitalnik kodPredpreverjanjeNastavitevNastavitveNastavitve...PredogledPrejšnjiPrejšnji zapisPrejšnje poljePrejšnji zavihekTiskIzpis poročila_Izpis...ProfilUrejevalnik profilovProfil:ZapriNarekovaj:Zapis shranjen.Zapisi niso izbrisani.Zapisi izbrisani.Veza_Veze...Povezani zapisiPonovno naloži/RazveljaviOdstrani "%s"Odstrani Odstrani datotekoOdstrani izbrani profilOdstrani izbran zapisOdstrani ta zaznamekPoročiloPrijava programske napakePoročilo...PoročilaRazličicaRazličica:ShraniShrani kot...Shrani kot...Shrani širino stolpcevShrani stanje drevesaShrani in ustvari novoShrani ta zapisShrani trenutno različicoSkenirajIščiIšči %sNastavitev omejitev iskanjaOmejitev iskanja...Poišči zapis IskalnikPreglej spremenjeno različicoIzberiIzberi datotekoIzberi barvoIzberi različicoIzberi vseIzberi nadrejeno vejoIzberi ukrepIzberi...Izberi/Aktiviraj trenutno vrsticoIzbrani zapisiIzbiraPošljiPošlji e-pošto z uporabo zapisaBližnjicePrikaži aktivne zapisePrikaži zaznamke filtrovPrikaži neaktivne zapisePrikaži navadno besediloPrikaži revizije...ČrkovanjeZadeva:PreklopiPreklopi pogledPreklopi pogledPredlogaBesediloBesedilna poljaBesedilo in ikoneZa naslednji ukrep se bodo zaprli vsi zavihki. Ali želiš nadaljevati?Število decimalnih mestVrednosti v polju "%s" niso veljavne.Ta zapis je bil spremenjen, ga želiš shraniti?Ta zapis je bil spremenjen med urejanjem.Za:DanesPrikaži/skrij meniRazširi/Skrči vrsticoVklopi/Izklopi izborPreveč poslanih zahtev. Poskusite kasneje.Orodna vrsticaPrevedi pogledPrevodDaNi bilo mogoče preveriti posodobitev nove različice programa.Ni mogoče samodejno dokončati e-poštnega vnosaNi možno nastaviti krajevnih nastavitev %sNi možno nastaviti stanja obrazcaPovrni izbran zapis PodčrtanoNeznanoNeznana glava stolpca "%s"Postavka neoznačena za brisanjeOdznači vseUporabi lokalizirano oblikoUporabnik:Uporabniško ime:VrednostPreglej dnevnik...Prikaži dnevni_k...TedenKako se imenuje ta izvoz?Širina:ČarovnikTrenutno se dela na podvojenih zapisih.Vseeno zapišilDaEn zapis mora biti izbran.Najprej morate izbrati datoteko za uvoz.Pred prevajanjem je potrebno shraniti zapis.Vaša izbira:_Ukrepi..._Dodaj_Samozaznava_Prekliči_PočistiZapri zavi_hek_Kopiraj URL_Izbriši izvozZ_briši..._Podvoji_Elektronska pošta...I_zvozi podatke...Uv_ozi podatke..._NovNas_lednji_Zapiski ...P_rejšnji_Tiskaj..._Veze...Ponovno naloži/Razvel_javi_OdstraniP_oročila..._Shrani_Shrani izvoz_IščiPreklopi po_gled_URL izvozdRazvijalski načinonemogoči uporabo nitipojdi nazajpojdi naprejubeleženje vsega na INFO ravnimmodularnost, razširljivost in varnostnaslednje letoPrejšnje letosdoloči alterntivno konfiguracijsko datotekoizberi datoteko dnevnikadoloči nivo beleženja: DEBUG, INFO, WARNING, ERROR, CRITICALdoloči uporabniško imedoloči ime in vrata strežniškega gostiteljatBlaž Bregartd././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685599.0 tryton-7.0.24/tryton/data/locale/sl/LC_MESSAGES/tryton.po0000644000175000017500000004706014517761237021110 0ustar00cedced# Slovenian (Slovenia) translations for tryton. # Copyright (C) 2010 B2CK # This file is distributed under the same license as the tryton project. # Venceslav Vezjak , 2010. msgid "" msgstr "Content-Type: text/plain; charset=utf-8\n" msgid "specify alternate config file" msgstr "določi alterntivno konfiguracijsko datoteko" msgid "development mode" msgstr "Razvijalski način" msgid "logging everything at INFO level" msgstr "beleženje vsega na INFO ravni" msgid "specify the log level: DEBUG, INFO, WARNING, ERROR, CRITICAL" msgstr "določi nivo beleženja: DEBUG, INFO, WARNING, ERROR, CRITICAL" msgid "specify the file used to output logging information" msgstr "izberi datoteko dnevnika" msgid "specify the login user" msgstr "določi uporabniško ime" msgid "specify the server hostname:port" msgstr "določi ime in vrata strežniškega gostitelja" msgid "disable thread usage" msgstr "onemogoči uporabo niti" #, python-format msgid "Unable to set locale %s" msgstr "Ni možno nastaviti krajevnih nastavitev %s" msgid ", " msgstr ", " msgid ",..." msgstr ",..." # ? #, python-format msgid "%s (%s)" msgstr "%s (%s)" msgid "Select your action" msgstr "Izberi ukrep" msgid "No action defined." msgstr "Nobenega ukrepa ni določenega." msgid "By: " msgstr "Od: " msgid "Selection" msgstr "Izbira" msgid "Cancel" msgstr "Prekliči" msgid "OK" msgstr "V redu" msgid "Your selection:" msgstr "Vaša izbira:" msgid "Save As..." msgstr "Shrani kot..." msgid "Do you want to proceed?" msgstr "Ali želite nadaljevati?" msgid "Always ignore this warning." msgstr "Vedno prezri to opozorilo." msgid "No" msgstr "Ne" msgid "Yes" msgstr "Da" msgid "Concurrency Exception" msgstr "Izjema sočasnega izvajanja" msgid "This record has been modified while you were editing it." msgstr "Ta zapis je bil spremenjen med urejanjem." msgid "Cancel saving" msgstr "Prekliči shranjevanje" msgid "Compare" msgstr "Primerjaj" msgid "See the modified version" msgstr "Preglej spremenjeno različico" msgid "Write Anyway" msgstr "Vseeno zapiši" msgid "Save your current version" msgstr "Shrani trenutno različico" #, python-format msgid "Compare: %s" msgstr "Primerjaj: %s" msgid "Close" msgstr "Zapri" msgid "Application Error" msgstr "Programska napaka" msgid "Report Bug" msgstr "Prijava programske napake" #, python-format msgid "Check URL: %s" msgstr "Preveri URL: %s" msgid "Unable to check for new version." msgstr "Ni bilo mogoče preveriti posodobitev nove različice programa." msgid "A new version is available!" msgstr "Nova različica programa je na voljo!" msgid "Download" msgstr "Prenesi" msgid "Could not get a session." msgstr "Ni mogoče vzpostaviti seje." msgid "Too many requests. Try again later." msgstr "Preveč poslanih zahtev. Poskusite kasneje." msgid "Not found." msgstr "Ni najdeno." msgid "Not Found." msgstr "Ni najdeno." msgid "..." msgstr "..." msgid "Search..." msgstr "Išči..." msgid "Create..." msgstr "Ustvari..." #, python-format msgid "Create \"%s\"..." msgstr "Ustvari \"%s\"..." msgid "Value" msgstr "Vrednost" msgid "Displayed value" msgstr "Prikazana vrednost" msgid "Format" msgstr "Oblika" msgid "Display format" msgstr "Oblika prikaza" msgid "Open the calendar" msgstr "Odpri koledar" msgid "Date Format" msgstr "Oblika datuma" msgid "Displayed date format" msgstr "Prikazana oblika datuma" msgid "y" msgstr "d" msgid "True" msgstr "Da" msgid "t" msgstr "t" msgid "False" msgstr "Ne" msgid "Digits" msgstr "Decimalna mesta" msgid "The number of decimal" msgstr "Število decimalnih mest" msgid "Template" msgstr "Predloga" msgid "Edit..." msgstr "Uredi ..." msgid "View Logs..." msgstr "Preglej dnevnik..." msgid "Attachments..." msgstr "Priloge..." msgid "Notes..." msgstr "Zapiski ..." msgid "Actions..." msgstr "_Ukrepi..." msgid "Relate..." msgstr "_Veze..." msgid "Report..." msgstr "Poročilo..." msgid "Print..." msgstr "_Izpis..." msgid "E-Mail..." msgstr "E-pošta..." msgid "Bold" msgstr "Krepko" msgid "Italic" msgstr "Poševno" msgid "Underline" msgstr "Podčrtano" msgid "Align Left" msgstr "Leva poravnava" msgid "Align Center" msgstr "Sredinska poravnava" msgid "Align Right" msgstr "Desna poravnava" msgid "Justify" msgstr "Obojestranska poravnava" msgid "Foreground Color" msgstr "Barva ospredja" msgid "Select a color" msgstr "Izberi barvo" msgid "Y" msgstr "l" msgid "M" msgstr "M" msgid "w" msgstr "t" msgid "d" msgstr "d" msgid "h" msgstr "u" msgid "m" msgstr "m" msgid "s" msgstr "s" msgid "Preferences..." msgstr "Nastavitve..." msgid "Toolbar" msgstr "Orodna vrstica" msgid "Default" msgstr "Privzeto" msgid "Text and Icons" msgstr "Besedilo in ikone" msgid "Text" msgstr "Besedilo" msgid "Icons" msgstr "Ikone" msgid "Form" msgstr "Obrazec" msgid "Save Column Width" msgstr "Shrani širino stolpcev" msgid "Save Tree State" msgstr "Shrani stanje drevesa" msgid "Spell Checking" msgstr "Črkovanje" msgid "Play Sound for Code Scanner" msgstr "Predvajaj zvok za čitalnik kod" msgid "PDA Mode" msgstr "Tablični način" msgid "Search Limit..." msgstr "Omejitev iskanja..." msgid "Check Version" msgstr "Preveri posodobitve" msgid "Options" msgstr "Možnosti" msgid "Documentation..." msgstr "Dokumentacija..." msgid "Keyboard Shortcuts..." msgstr "Bližnjice na tipkovnici..." msgid "About..." msgstr "Vizitka..." msgid "Help" msgstr "Pomoč" msgid "No result found." msgstr "Brez rezultatov." msgid "Favorites" msgstr "Priljubljene" msgid "Manage..." msgstr "Upravljaj..." msgid "Action" msgstr "Ukrep" msgid "" "The following action requires to close all tabs.\n" "Do you want to continue?" msgstr "" "Za naslednji ukrep se bodo zaprli vsi zavihki.\n" "Ali želiš nadaljevati?" msgid "Application Shortcuts" msgstr "Bližnjice na tipkovnici" msgid "Global" msgstr "Globalno" msgid "Preferences" msgstr "Nastavitve" msgid "Search menu" msgstr "Iskalnik" msgid "Toggle menu" msgstr "Prikaži/skrij meni" msgid "Previous tab" msgstr "Prejšnji zavihek" msgid "Next tab" msgstr "Naslednji zavihek" msgid "Shortcuts" msgstr "Bližnjice" msgid "Quit" msgstr "Zapri" msgid "Edition Shortcuts" msgstr "Uredi bližnjice" msgid "Text Entries" msgstr "Besedilna polja" msgid "Cut selected text" msgstr "Izreži vsebino" msgid "Copy selected text" msgstr "Kopiraj vsebino" msgid "Paste copied text" msgstr "Prilepi vsebino" msgid "Next entry" msgstr "Naslednje polje" msgid "Previous entry" msgstr "Prejšnje polje" msgid "Relation Entries" msgstr "Povezani zapisi" msgid "Create new relation" msgstr "Dodajanje nove veze" msgid "Open/Search relation" msgstr "Urejanje/iskanje veze" msgid "List Entries" msgstr "Polja s seznami" msgid "Switch view" msgstr "Preklopi pogled" msgid "Create/Select new line" msgstr "Ustvari/Izberi novo vrstico" msgid "Open relation" msgstr "Urejenje postavke" msgid "Mark line for deletion/removal" msgstr "Izberi vrstico za brisanje/odstranitev" msgid "Mark line for removal" msgstr "Označi vrstico za odstranitev" msgid "Unmark line for deletion" msgstr "Postavka neoznačena za brisanje" msgid "List/Tree Shortcuts" msgstr "Bližnjice seznama/drevesnega pogleda" msgid "Move Cursor" msgstr "Premiki" msgid "Move right" msgstr "Premik desno" msgid "Move left" msgstr "Premik levo" msgid "Move up" msgstr "Premik gor" msgid "Move down" msgstr "Premik dol" msgid "Move up of one page" msgstr "Premik za eno stran gor" msgid "Move down of one page" msgstr "Premik za eno stran dol" msgid "Move to top" msgstr "Premik na začetek" msgid "Move to bottom" msgstr "Premik na konec" msgid "Move to parent" msgstr "Premik na nadrejeno vejo" msgid "Edition" msgstr "Urejanje" msgid "Copy selected rows" msgstr "Kopiraj izbrane vrstice" msgid "Insert copied rows" msgstr "Vstavi kopirane vrstice" msgid "Select all" msgstr "Izberi vse" msgid "Unselect all" msgstr "Odznači vse" msgid "Select parent" msgstr "Izberi nadrejeno vejo" msgid "Select/Activate current row" msgstr "Izberi/Aktiviraj trenutno vrstico" msgid "Toggle selection" msgstr "Vklopi/Izklopi izbor" msgid "Expand/Collapse" msgstr "Razširi/Skrči" msgid "Expand row" msgstr "Razširi vrstico" msgid "Collapse row" msgstr "Skrči vrstico" msgid "Toggle row" msgstr "Razširi/Skrči vrstico" msgid "Collapse all rows" msgstr "Skrči vse vrstice" msgid "Expand all rows" msgstr "Razširi vse vrstice" msgid "Close Tab" msgstr "Zapri zavihek" msgid "modularity, scalability and security" msgstr "modularnost, razširljivost in varnost" msgid "translator-credits" msgstr "Blaž Bregar" #, python-format msgid "Attachments (%s)" msgstr "Priloge (%s)" msgid "Code Scanner" msgstr "Čitalnik kod" msgid "Code" msgstr "Koda" msgid "Profile Editor" msgstr "Urejevalnik profilov" msgid "Profile" msgstr "Profil" msgid "Add new profile" msgstr "Dodaj nov profil" msgid "Remove selected profile" msgstr "Odstrani izbrani profil" msgid "Host:" msgstr "Strežnik:" msgid "Database:" msgstr "Podatkovna baza:" msgid "Fetching databases list" msgstr "Pridobivanje seznama podatkovnih baz" msgid "Username:" msgstr "Uporabniško ime:" msgid "Incompatible version of the server." msgstr "Nezdružljiva različica strežnika." msgid "Could not connect to the server." msgstr "Ni se mogoče povezati s strežnikom." msgid "Login" msgstr "Prijava" msgid "_Cancel" msgstr "_Prekliči" msgid "Cancel connection to the Tryton server" msgstr "Prekliči povezavo do Tryton strežnika" msgid "C_onnect" msgstr "P_oveži" msgid "Connect the Tryton server" msgstr "Poveži se s Tryton strežnikom" msgid "Profile:" msgstr "Profil:" msgid "Host / Database information" msgstr "Strežnik in podatkovna baza" msgid "User name:" msgstr "Uporabnik:" msgid "Unable to complete email entry" msgstr "Ni mogoče samodejno dokončati e-poštnega vnosa" #, python-format msgid "E-mail %s" msgstr "E-pošta %s" msgid "To:" msgstr "Za:" msgid "Cc:" msgstr "Cc:" msgid "Bcc:" msgstr "Bcc:" msgid "Subject:" msgstr "Zadeva:" msgid "Body" msgstr "Telo besedila" msgid "Reports" msgstr "Poročila" msgid "Attachments" msgstr "Priloge" msgid "Files" msgstr "Datoteke" msgid "Send" msgstr "Pošlji" msgid "Select File" msgstr "Izberi datoteko" msgid "Remove File" msgstr "Odstrani datoteko" msgid "Select" msgstr "Izberi" msgid "Add..." msgstr "Dodaj..." msgid "Preview" msgstr "Predogled" msgid "Previous" msgstr "Prejšnji" msgid "Next" msgstr "Naslednji" #, python-format msgid "Attachment (%s)" msgstr "Priloga (%s)" #, python-format msgid "Note (%d/%d)" msgstr "Zapisek (%d/%d)" msgid "You have to select one record." msgstr "En zapis mora biti izbran." msgid "Are you sure to remove this record?" msgstr "Ali res želiš izbrisati ta zapis?" msgid "Are you sure to remove those records?" msgstr "Ali res želiš izbrisati te zapise?" msgid "Records not removed." msgstr "Zapisi niso izbrisani." msgid "Records removed." msgstr "Zapisi izbrisani." msgid "Working now on the duplicated record(s)." msgstr "Trenutno se dela na podvojenih zapisih." msgid "Record saved." msgstr "Zapis shranjen." msgid "" "This record has been modified\n" "do you want to save it?" msgstr "" "Ta zapis je bil spremenjen,\n" "ga želiš shraniti?" msgid "Launch action" msgstr "Zaženi ukrep" msgid "Relate" msgstr "Veza" msgid "Open related records" msgstr "Odpri povezane zapise" msgid "Report" msgstr "Poročilo" msgid "Open report" msgstr "Odpri poročilo" msgid "Print" msgstr "Tisk" msgid "Print report" msgstr "Izpis poročila" msgid "_Copy URL" msgstr "_Kopiraj URL" msgid "Copy URL into clipboard" msgstr "Kopiraj URL na odložišče" msgid "Unknown" msgstr "Neznano" msgid "Limit" msgstr "Omejitev" msgid "Search Limit Settings" msgstr "Nastavitev omejitev iskanja" msgid "Limit:" msgstr "Omejitev:" # ? #, python-format msgid "Logs (%s)" msgstr "Dnevniki (%s)" msgid "Model:" msgstr "Model:" msgid "ID:" msgstr "ID:" msgid "Created by:" msgstr "Ustvaril:" msgid "Created at:" msgstr "Ustvarjeno ob:" msgid "Last Modified by:" msgstr "Nazadnje spremenil:" msgid "Last Modified at:" msgstr "Nazadnje spremenjeno:" #, python-format msgid "Notes (%s)" msgstr "Zapiski (%s)" msgid "Edit User Preferences" msgstr "Uredi uporabniške nastavitve" msgid "Preference" msgstr "Nastavitev" msgid "Revision" msgstr "Različica" msgid "Select a revision" msgstr "Izberi različico" msgid "Revision:" msgstr "Različica:" msgid "_Switch View" msgstr "Preklopi po_gled" msgid "Switch View" msgstr "Preklopi pogled" msgid "_Previous" msgstr "P_rejšnji" msgid "Previous Record" msgstr "Prejšnji zapis" msgid "_Next" msgstr "Nas_lednji" msgid "Next Record" msgstr "Naslednji zapis" msgid "_Search" msgstr "_Išči" msgid "_New" msgstr "_Nov" msgid "Create a new record" msgstr "Ustvari nov zapis" msgid "_Save" msgstr "_Shrani" msgid "Save this record" msgstr "Shrani ta zapis" msgid "_Reload/Undo" msgstr "Ponovno naloži/Razvel_javi" msgid "Reload/Undo" msgstr "Ponovno naloži/Razveljavi" msgid "_Duplicate" msgstr "_Podvoji" msgid "_Delete..." msgstr "Z_briši..." msgid "View _Logs..." msgstr "Prikaži dnevni_k..." msgid "Show revisions..." msgstr "Prikaži revizije..." msgid "A_ttachments..." msgstr "P_riponke..." msgid "Add an attachment to the record" msgstr "Dodaj prilogo zapisu" msgid "_Notes..." msgstr "_Zapiski ..." msgid "Add a note to the record" msgstr "K zapisu dodaj zabeležko" msgid "_Actions..." msgstr "_Ukrepi..." msgid "_Relate..." msgstr "_Veze..." msgid "_Report..." msgstr "P_oročila..." msgid "_Print..." msgstr "_Tiskaj..." msgid "_E-Mail..." msgstr "_Elektronska pošta..." msgid "Send an e-mail using the record" msgstr "Pošlji e-pošto z uporabo zapisa" msgid "_Export Data..." msgstr "I_zvozi podatke..." msgid "_Import Data..." msgstr "Uv_ozi podatke..." msgid "Copy _URL..." msgstr "_Kopiraj URL ..." msgid "_Close Tab" msgstr "Zapri zavi_hek" msgid "All fields" msgstr "Vsa polja" msgid "_Add" msgstr "_Dodaj" msgid "_Remove" msgstr "_Odstrani" msgid "_Clear" msgstr "_Počisti" msgid "Fields selected" msgstr "Izbrana polja" msgid "CSV Parameters" msgstr "CSV parametri" msgid "Delimiter:" msgstr "Ločilo:" msgid "Quote char:" msgstr "Narekovaj:" msgid "Encoding:" msgstr "Kodiranje:" msgid "Use locale format" msgstr "Uporabi lokalizirano obliko" msgid "Field name" msgstr "Naziv polja" #, python-format msgid "CSV Export: %s" msgstr "CSV izvoz: %s" msgid "_Save Export" msgstr "_Shrani izvoz" msgid "_URL Export" msgstr "_URL izvoz" msgid "_Delete Export" msgstr "_Izbriši izvoz" msgid "Predefined exports" msgstr "Predefinirani izvozi" msgid "Name" msgstr "Naziv" msgid "Open" msgstr "Odpri" msgid "Save" msgstr "Shrani" msgid "Listed Records" msgstr "Prikazani zapisi" msgid "Selected Records" msgstr "Izbrani zapisi" msgid "Ignore search limit" msgstr "Prezri omejitev iskanja" msgid "Add field names" msgstr "Dodaj imena polj" #, python-format msgid "%s (string)" msgstr "%s (niz)" #, python-format msgid "%s (model name)" msgstr "%s (ime modela)" #, python-format msgid "%s/Record Name" msgstr "%s/Naziv zapisa" msgid "What is the name of this export?" msgstr "Kako se imenuje ta izvoz?" #, python-format msgid "Override '%s' definition?" msgstr "Prepišem definicijo izvoza '%s'?" #, python-format msgid "%d record saved." msgstr "%d zapis shranjen." #, python-format msgid "%d records saved." msgstr "%d zapisov shranjenih." msgid "Export failed" msgstr "Izvoz ni uspel" msgid "Link" msgstr "Povezava" msgid "Delete" msgstr "Zbriši" msgid "Discard changes" msgstr "Zavrzi spremembe" msgid "Save and New" msgstr "Shrani in ustvari novo" msgid "Add and New" msgstr "Dodaj in ustvari novo" msgid "Add" msgstr "Dodaj" msgid "Apply changes" msgstr "Uporabi spremembe" msgid "Switch" msgstr "Preklopi" msgid "Remove " msgstr "Odstrani " msgid "Create a new record " msgstr "Ustvari nov zapis " msgid "Delete selected record " msgstr "Zbriši izbran zapis " msgid "Undelete selected record " msgstr "Povrni izbran zapis " #, python-format msgid "CSV Import: %s" msgstr "CSV uvoz: %s" msgid "_Auto-Detect" msgstr "_Samozaznava" msgid "File to Import:" msgstr "Datoteka za uvoz:" msgid "Open..." msgstr "Odpri..." msgid "Lines to Skip:" msgstr "Izpuščene vrstice:" msgid "You must select an import file first." msgstr "Najprej morate izbrati datoteko za uvoz." msgid "Detection failed" msgstr "Zaznavanje datoteke ni uspelo" #, python-format msgid "Unknown column header \"%s\"" msgstr "Neznana glava stolpca \"%s\"" msgid "Error" msgstr "Napaka" msgid "Import failed" msgstr "Uvoz ni uspel" #, python-format msgid "%d record imported." msgstr "%d zapis uvožen." #, python-format msgid "%d records imported." msgstr "%d zapisov uvoženih." msgid "Search" msgstr "Išči" msgid "New" msgstr "Novo" #, python-format msgid "Search %s" msgstr "Išči %s" msgid "Wizard" msgstr "Čarovnik" msgid "ID" msgstr "ID" msgid "Created by" msgstr "Ustvaril" msgid "Created at" msgstr "Ustvarjeno ob" msgid "Edited by" msgstr "Uredil" msgid "Edited at" msgstr "Urejeno ob" msgid "Unable to set view tree state" msgstr "Ni možno nastaviti stanja obrazca" #, python-format msgid "\"%s\" is not valid according to its domain." msgstr "Polje \"%s\" ni veljavno glede na svojo domeno." #, python-format msgid "\"%s\" is required." msgstr "Polje \"%s\" je obvezno." #, python-format msgid "The values of \"%s\" are not valid." msgstr "Vrednosti v polju \"%s\" niso veljavne." msgid "Pre-validation" msgstr "Predpreverjanje" msgid ":" msgstr ":" msgid "Scan" msgstr "Skeniraj" msgid "Image Size" msgstr "Velikost podobe" msgid "Width:" msgstr "Širina:" msgid "Height:" msgstr "Velikost:" msgid "PNG image (*.png)" msgstr "PNG podoba (*.png)" msgid "Save As" msgstr "Shrani kot..." msgid "Image size too large." msgstr "Podoba je prevelika." msgid "Copy" msgstr "Kopiraj" msgid "Paste" msgstr "Prilepi" msgid ".." msgstr ".." msgid "Open filters" msgstr "Odpri filtre" msgid "Show bookmarks of filters" msgstr "Prikaži zaznamke filtrov" msgid "Remove this bookmark" msgstr "Odstrani ta zaznamek" msgid "Bookmark this filter" msgstr "Shrani ta filter" msgid "Show active records" msgstr "Prikaži aktivne zapise" msgid "Show inactive records" msgstr "Prikaži neaktivne zapise" msgid "Bookmark Name:" msgstr "Ime zaznamka:" msgid "Find" msgstr "Najdi" msgid "Today" msgstr "Danes" msgid "go back" msgstr "pojdi nazaj" msgid "go forward" msgstr "pojdi naprej" msgid "previous year" msgstr "Prejšnje leto" msgid "next year" msgstr "naslednje leto" msgid "Day" msgstr "Dan" msgid "Week" msgstr "Teden" msgid "Month" msgstr "Mesec" msgid "Select..." msgstr "Izberi..." msgid "Clear" msgstr "Počisti" msgid "All files" msgstr "Vse datoteke" msgid "Show plain text" msgstr "Prikaži navadno besedilo" msgid "Add value" msgstr "Dodaj vrednost" #, python-format msgid "Remove \"%s\"" msgstr "Odstrani \"%s\"" msgid "Images" msgstr "Podobe" msgid "Add existing record" msgstr "Dodaj obstoječi zapis" msgid "Remove selected record" msgstr "Odstrani izbran zapis" msgid "Open the record " msgstr "Odpri zapis " msgid "Clear the field " msgstr "Počisti polje " msgid "Search a record " msgstr "Poišči zapis " msgid "Edit selected record" msgstr "Uredi izbran zapis" msgid "Delete selected record" msgstr "Izbriši izbran zapis" #, python-format msgid "%s%%" msgstr "%s%%" msgid "Choose a language" msgstr "Izberi jezik" msgid "Translation" msgstr "Prevod" msgid "Edit" msgstr "Uredi" msgid "Fuzzy" msgstr "Nejasno" msgid "You need to save the record before adding translations." msgstr "Pred prevajanjem je potrebno shraniti zapis." msgid "No other language available." msgstr "Drugih jezikov ni na voljo." msgid "#ERROR" msgstr "#NAPAKA" msgid "Translate view" msgstr "Prevedi pogled" ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1745680285.1274436 tryton-7.0.24/tryton/data/locale/tr/0000755000175000017500000000000015003173635015412 5ustar00cedced././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1745680285.167444 tryton-7.0.24/tryton/data/locale/tr/LC_MESSAGES/0000755000175000017500000000000015003173635017177 5ustar00cedced././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1745680279.0 tryton-7.0.24/tryton/data/locale/tr/LC_MESSAGES/tryton.mo0000644000175000017500000000324015003173627021073 0ustar00cedced  %0 CM er < &G ;@Qgw !&=# a%s (%s), .....:Always ignore this warning.By: CompareDo you want to proceed?ErrorLogs (%s)No action defined.Report BugSelect your actionSelectionUnable to set locale %sWrite AnywayYour selection:development modelogging everything at INFO levelspecify alternate config filespecify the log level: DEBUG, INFO, WARNING, ERROR, CRITICALspecify the login userspecify the server hostname:portProject-Id-Version: PROJECT VERSION Report-Msgid-Bugs-To: EMAIL@ADDRESS POT-Creation-Date: 2025-04-26 17:11+0200 PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE Last-Translator: FULL NAME Language: tr Language-Team: tr Plural-Forms: nplurals=1; plural=0; MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: 8bit Generated-By: Babel 2.17.0 %s (%s), .....:Bu uyarıyı daima ihmal et.Tarafından: Karşılaştırİlerlemek istiyor musunuz?HataGünlükler (%s)İşlem belirtilmedi.Hatayı Raporlaİşleminizi seçinSeçimLokal %s ayarlanamadıTüm Durumlarda YazSeçiminiz:Geliştirme ModuTamamına INFO seviyesinde girişAlternative config dosyasını belirleLog seviyesini belirle: DEBUG, INFO, WARNING, ERROR, CRITICALGiriş kullanıcısını belirleServer hostname belirle: port././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685599.0 tryton-7.0.24/tryton/data/locale/tr/LC_MESSAGES/tryton.po0000644000175000017500000003413714517761237021120 0ustar00cedced# Translations template for tryton. # Copyright (C) 2017 Tryton # This file is distributed under the same license as the tryton project. # FIRST AUTHOR , 2017. msgid "" msgstr "Content-Type: text/plain; charset=utf-8\n" msgid "specify alternate config file" msgstr "Alternative config dosyasını belirle" msgid "development mode" msgstr "Geliştirme Modu" msgid "logging everything at INFO level" msgstr "Tamamına INFO seviyesinde giriş" msgid "specify the log level: DEBUG, INFO, WARNING, ERROR, CRITICAL" msgstr "Log seviyesini belirle: DEBUG, INFO, WARNING, ERROR, CRITICAL" msgid "specify the file used to output logging information" msgstr "" msgid "specify the login user" msgstr "Giriş kullanıcısını belirle" msgid "specify the server hostname:port" msgstr "Server hostname belirle: port" msgid "disable thread usage" msgstr "" #, python-format msgid "Unable to set locale %s" msgstr "Lokal %s ayarlanamadı" msgid ", " msgstr ", " msgid ",..." msgstr "" #, python-format msgid "%s (%s)" msgstr "%s (%s)" msgid "Select your action" msgstr "İşleminizi seçin" msgid "No action defined." msgstr "İşlem belirtilmedi." msgid "By: " msgstr "Tarafından: " msgid "Selection" msgstr "Seçim" msgid "Cancel" msgstr "" msgid "OK" msgstr "" msgid "Your selection:" msgstr "Seçiminiz:" #, fuzzy msgid "Save As..." msgstr "Save As..." msgid "Do you want to proceed?" msgstr "İlerlemek istiyor musunuz?" msgid "Always ignore this warning." msgstr "Bu uyarıyı daima ihmal et." msgid "No" msgstr "" msgid "Yes" msgstr "" msgid "Concurrency Exception" msgstr "" msgid "This record has been modified while you were editing it." msgstr "" msgid "Cancel saving" msgstr "" msgid "Compare" msgstr "Karşılaştır" msgid "See the modified version" msgstr "" msgid "Write Anyway" msgstr "Tüm Durumlarda Yaz" msgid "Save your current version" msgstr "" #, fuzzy, python-format msgid "Compare: %s" msgstr "Karşılaştır: %s" msgid "Close" msgstr "" #, fuzzy msgid "Application Error" msgstr "Uygulama Hatası." msgid "Report Bug" msgstr "Hatayı Raporla" #, python-format msgid "Check URL: %s" msgstr "" msgid "Unable to check for new version." msgstr "" msgid "A new version is available!" msgstr "" msgid "Download" msgstr "" msgid "Could not get a session." msgstr "" msgid "Too many requests. Try again later." msgstr "" msgid "Not found." msgstr "" msgid "Not Found." msgstr "" msgid "..." msgstr "..." msgid "Search..." msgstr "" msgid "Create..." msgstr "" #, python-format msgid "Create \"%s\"..." msgstr "" msgid "Value" msgstr "" msgid "Displayed value" msgstr "" msgid "Format" msgstr "" msgid "Display format" msgstr "" msgid "Open the calendar" msgstr "" msgid "Date Format" msgstr "" msgid "Displayed date format" msgstr "" msgid "y" msgstr "" msgid "True" msgstr "" msgid "t" msgstr "" msgid "False" msgstr "" msgid "Digits" msgstr "" msgid "The number of decimal" msgstr "" msgid "Template" msgstr "" msgid "Edit..." msgstr "" msgid "View Logs..." msgstr "" msgid "Attachments..." msgstr "" msgid "Notes..." msgstr "" msgid "Actions..." msgstr "" msgid "Relate..." msgstr "" msgid "Report..." msgstr "" msgid "Print..." msgstr "" msgid "E-Mail..." msgstr "" msgid "Bold" msgstr "" msgid "Italic" msgstr "" msgid "Underline" msgstr "" msgid "Align Left" msgstr "" msgid "Align Center" msgstr "" msgid "Align Right" msgstr "" msgid "Justify" msgstr "" msgid "Foreground Color" msgstr "" msgid "Select a color" msgstr "" msgid "Y" msgstr "" msgid "M" msgstr "" msgid "w" msgstr "" msgid "d" msgstr "" msgid "h" msgstr "" msgid "m" msgstr "" msgid "s" msgstr "" msgid "Preferences..." msgstr "" msgid "Toolbar" msgstr "" msgid "Default" msgstr "" msgid "Text and Icons" msgstr "" msgid "Text" msgstr "" msgid "Icons" msgstr "" msgid "Form" msgstr "" msgid "Save Column Width" msgstr "" msgid "Save Tree State" msgstr "" msgid "Spell Checking" msgstr "" msgid "Play Sound for Code Scanner" msgstr "" msgid "PDA Mode" msgstr "" msgid "Search Limit..." msgstr "" msgid "Check Version" msgstr "" msgid "Options" msgstr "" msgid "Documentation..." msgstr "" msgid "Keyboard Shortcuts..." msgstr "" msgid "About..." msgstr "" msgid "Help" msgstr "" msgid "No result found." msgstr "" msgid "Favorites" msgstr "" msgid "Manage..." msgstr "" msgid "Action" msgstr "" msgid "" "The following action requires to close all tabs.\n" "Do you want to continue?" msgstr "" msgid "Application Shortcuts" msgstr "" msgid "Global" msgstr "" msgid "Preferences" msgstr "" msgid "Search menu" msgstr "" msgid "Toggle menu" msgstr "" msgid "Previous tab" msgstr "" msgid "Next tab" msgstr "" msgid "Shortcuts" msgstr "" msgid "Quit" msgstr "" msgid "Edition Shortcuts" msgstr "" msgid "Text Entries" msgstr "" msgid "Cut selected text" msgstr "" msgid "Copy selected text" msgstr "" msgid "Paste copied text" msgstr "" msgid "Next entry" msgstr "" msgid "Previous entry" msgstr "" msgid "Relation Entries" msgstr "" msgid "Create new relation" msgstr "" msgid "Open/Search relation" msgstr "" msgid "List Entries" msgstr "" msgid "Switch view" msgstr "" msgid "Create/Select new line" msgstr "" msgid "Open relation" msgstr "" msgid "Mark line for deletion/removal" msgstr "" msgid "Mark line for removal" msgstr "" msgid "Unmark line for deletion" msgstr "" msgid "List/Tree Shortcuts" msgstr "" msgid "Move Cursor" msgstr "" msgid "Move right" msgstr "" msgid "Move left" msgstr "" msgid "Move up" msgstr "" msgid "Move down" msgstr "" msgid "Move up of one page" msgstr "" msgid "Move down of one page" msgstr "" msgid "Move to top" msgstr "" msgid "Move to bottom" msgstr "" msgid "Move to parent" msgstr "" msgid "Edition" msgstr "" msgid "Copy selected rows" msgstr "" msgid "Insert copied rows" msgstr "" msgid "Select all" msgstr "" msgid "Unselect all" msgstr "" msgid "Select parent" msgstr "" msgid "Select/Activate current row" msgstr "" msgid "Toggle selection" msgstr "" msgid "Expand/Collapse" msgstr "" msgid "Expand row" msgstr "" msgid "Collapse row" msgstr "" msgid "Toggle row" msgstr "" msgid "Collapse all rows" msgstr "" msgid "Expand all rows" msgstr "" msgid "Close Tab" msgstr "" msgid "modularity, scalability and security" msgstr "" msgid "translator-credits" msgstr "" #, python-format msgid "Attachments (%s)" msgstr "" msgid "Code Scanner" msgstr "" msgid "Code" msgstr "" msgid "Profile Editor" msgstr "" msgid "Profile" msgstr "" msgid "Add new profile" msgstr "" msgid "Remove selected profile" msgstr "" msgid "Host:" msgstr "" msgid "Database:" msgstr "" msgid "Fetching databases list" msgstr "" msgid "Username:" msgstr "" msgid "Incompatible version of the server." msgstr "" msgid "Could not connect to the server." msgstr "" msgid "Login" msgstr "" msgid "_Cancel" msgstr "" msgid "Cancel connection to the Tryton server" msgstr "" msgid "C_onnect" msgstr "" msgid "Connect the Tryton server" msgstr "" msgid "Profile:" msgstr "" msgid "Host / Database information" msgstr "" msgid "User name:" msgstr "" #, fuzzy msgid "Unable to complete email entry" msgstr "Lokal %s ayarlanamadı" #, python-format msgid "E-mail %s" msgstr "" msgid "To:" msgstr "" msgid "Cc:" msgstr "" msgid "Bcc:" msgstr "" msgid "Subject:" msgstr "" msgid "Body" msgstr "" #, fuzzy msgid "Reports" msgstr "Hatayı Raporla" msgid "Attachments" msgstr "" msgid "Files" msgstr "" msgid "Send" msgstr "" #, fuzzy msgid "Select File" msgstr "Seçim" msgid "Remove File" msgstr "" msgid "Select" msgstr "" msgid "Add..." msgstr "" msgid "Preview" msgstr "" msgid "Previous" msgstr "" msgid "Next" msgstr "" #, python-format msgid "Attachment (%s)" msgstr "" #, python-format msgid "Note (%d/%d)" msgstr "" msgid "You have to select one record." msgstr "" msgid "Are you sure to remove this record?" msgstr "" msgid "Are you sure to remove those records?" msgstr "" msgid "Records not removed." msgstr "" msgid "Records removed." msgstr "" msgid "Working now on the duplicated record(s)." msgstr "" msgid "Record saved." msgstr "" msgid "" "This record has been modified\n" "do you want to save it?" msgstr "" msgid "Launch action" msgstr "" msgid "Relate" msgstr "" msgid "Open related records" msgstr "" msgid "Report" msgstr "" msgid "Open report" msgstr "" msgid "Print" msgstr "" msgid "Print report" msgstr "" msgid "_Copy URL" msgstr "" msgid "Copy URL into clipboard" msgstr "" msgid "Unknown" msgstr "" msgid "Limit" msgstr "" msgid "Search Limit Settings" msgstr "" msgid "Limit:" msgstr "" #, python-format msgid "Logs (%s)" msgstr "Günlükler (%s)" msgid "Model:" msgstr "" msgid "ID:" msgstr "" msgid "Created by:" msgstr "" msgid "Created at:" msgstr "" msgid "Last Modified by:" msgstr "" msgid "Last Modified at:" msgstr "" #, python-format msgid "Notes (%s)" msgstr "" msgid "Edit User Preferences" msgstr "" msgid "Preference" msgstr "" msgid "Revision" msgstr "" msgid "Select a revision" msgstr "" msgid "Revision:" msgstr "" msgid "_Switch View" msgstr "" msgid "Switch View" msgstr "" msgid "_Previous" msgstr "" msgid "Previous Record" msgstr "" msgid "_Next" msgstr "" msgid "Next Record" msgstr "" msgid "_Search" msgstr "" msgid "_New" msgstr "" msgid "Create a new record" msgstr "" msgid "_Save" msgstr "" msgid "Save this record" msgstr "" msgid "_Reload/Undo" msgstr "" msgid "Reload/Undo" msgstr "" msgid "_Duplicate" msgstr "" msgid "_Delete..." msgstr "" msgid "View _Logs..." msgstr "" msgid "Show revisions..." msgstr "" msgid "A_ttachments..." msgstr "" msgid "Add an attachment to the record" msgstr "" msgid "_Notes..." msgstr "" msgid "Add a note to the record" msgstr "" msgid "_Actions..." msgstr "" msgid "_Relate..." msgstr "" msgid "_Report..." msgstr "" msgid "_Print..." msgstr "" msgid "_E-Mail..." msgstr "" msgid "Send an e-mail using the record" msgstr "" msgid "_Export Data..." msgstr "" msgid "_Import Data..." msgstr "" msgid "Copy _URL..." msgstr "" msgid "_Close Tab" msgstr "" msgid "All fields" msgstr "" msgid "_Add" msgstr "" msgid "_Remove" msgstr "" msgid "_Clear" msgstr "" msgid "Fields selected" msgstr "" msgid "CSV Parameters" msgstr "" msgid "Delimiter:" msgstr "" msgid "Quote char:" msgstr "" msgid "Encoding:" msgstr "" msgid "Use locale format" msgstr "" msgid "Field name" msgstr "" #, python-format msgid "CSV Export: %s" msgstr "" msgid "_Save Export" msgstr "" msgid "_URL Export" msgstr "" msgid "_Delete Export" msgstr "" msgid "Predefined exports" msgstr "" msgid "Name" msgstr "" msgid "Open" msgstr "" msgid "Save" msgstr "" msgid "Listed Records" msgstr "" msgid "Selected Records" msgstr "" msgid "Ignore search limit" msgstr "" msgid "Add field names" msgstr "" #, python-format msgid "%s (string)" msgstr "" #, python-format msgid "%s (model name)" msgstr "" #, python-format msgid "%s/Record Name" msgstr "" msgid "What is the name of this export?" msgstr "" #, python-format msgid "Override '%s' definition?" msgstr "" #, python-format msgid "%d record saved." msgstr "" #, python-format msgid "%d records saved." msgstr "" msgid "Export failed" msgstr "" msgid "Link" msgstr "" msgid "Delete" msgstr "" msgid "Discard changes" msgstr "" msgid "Save and New" msgstr "" msgid "Add and New" msgstr "" msgid "Add" msgstr "" msgid "Apply changes" msgstr "" msgid "Switch" msgstr "" msgid "Remove " msgstr "" msgid "Create a new record " msgstr "" msgid "Delete selected record " msgstr "" msgid "Undelete selected record " msgstr "" #, python-format msgid "CSV Import: %s" msgstr "" msgid "_Auto-Detect" msgstr "" msgid "File to Import:" msgstr "" msgid "Open..." msgstr "" msgid "Lines to Skip:" msgstr "" msgid "You must select an import file first." msgstr "" msgid "Detection failed" msgstr "" #, python-format msgid "Unknown column header \"%s\"" msgstr "" msgid "Error" msgstr "Hata" msgid "Import failed" msgstr "" #, python-format msgid "%d record imported." msgstr "" #, python-format msgid "%d records imported." msgstr "" msgid "Search" msgstr "" msgid "New" msgstr "" #, python-format msgid "Search %s" msgstr "" msgid "Wizard" msgstr "" msgid "ID" msgstr "" msgid "Created by" msgstr "" msgid "Created at" msgstr "" msgid "Edited by" msgstr "" msgid "Edited at" msgstr "" msgid "Unable to set view tree state" msgstr "" #, python-format msgid "\"%s\" is not valid according to its domain." msgstr "" #, python-format msgid "\"%s\" is required." msgstr "" #, python-format msgid "The values of \"%s\" are not valid." msgstr "" msgid "Pre-validation" msgstr "" msgid ":" msgstr ":" msgid "Scan" msgstr "" msgid "Image Size" msgstr "" msgid "Width:" msgstr "" msgid "Height:" msgstr "" msgid "PNG image (*.png)" msgstr "" msgid "Save As" msgstr "" msgid "Image size too large." msgstr "" msgid "Copy" msgstr "" msgid "Paste" msgstr "" msgid ".." msgstr ".." msgid "Open filters" msgstr "" msgid "Show bookmarks of filters" msgstr "" msgid "Remove this bookmark" msgstr "" msgid "Bookmark this filter" msgstr "" msgid "Show active records" msgstr "" msgid "Show inactive records" msgstr "" msgid "Bookmark Name:" msgstr "" msgid "Find" msgstr "" msgid "Today" msgstr "" msgid "go back" msgstr "" msgid "go forward" msgstr "" msgid "previous year" msgstr "" msgid "next year" msgstr "" msgid "Day" msgstr "" msgid "Week" msgstr "" msgid "Month" msgstr "" msgid "Select..." msgstr "" msgid "Clear" msgstr "" msgid "All files" msgstr "" msgid "Show plain text" msgstr "" msgid "Add value" msgstr "" #, python-format msgid "Remove \"%s\"" msgstr "" msgid "Images" msgstr "" msgid "Add existing record" msgstr "" msgid "Remove selected record" msgstr "" msgid "Open the record " msgstr "" msgid "Clear the field " msgstr "" msgid "Search a record " msgstr "" msgid "Edit selected record" msgstr "" msgid "Delete selected record" msgstr "" #, python-format msgid "%s%%" msgstr "" msgid "Choose a language" msgstr "" msgid "Translation" msgstr "" msgid "Edit" msgstr "" msgid "Fuzzy" msgstr "" msgid "You need to save the record before adding translations." msgstr "" msgid "No other language available." msgstr "" msgid "#ERROR" msgstr "" msgid "Translate view" msgstr "" ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685599.0 tryton-7.0.24/tryton/data/locale/tryton.pot0000644000175000017500000003245714517761237017075 0ustar00cedcedmsgid "specify alternate config file" msgstr "" msgid "development mode" msgstr "" msgid "logging everything at INFO level" msgstr "" msgid "specify the log level: DEBUG, INFO, WARNING, ERROR, CRITICAL" msgstr "" msgid "specify the file used to output logging information" msgstr "" msgid "specify the login user" msgstr "" msgid "specify the server hostname:port" msgstr "" msgid "disable thread usage" msgstr "" #, python-format msgid "Unable to set locale %s" msgstr "" msgid ", " msgstr "" msgid ",..." msgstr "" #, python-format msgid "%s (%s)" msgstr "" msgid "Select your action" msgstr "" msgid "No action defined." msgstr "" msgid "By: " msgstr "" msgid "Selection" msgstr "" msgid "Cancel" msgstr "" msgid "OK" msgstr "" msgid "Your selection:" msgstr "" msgid "Save As..." msgstr "" msgid "Do you want to proceed?" msgstr "" msgid "Always ignore this warning." msgstr "" msgid "No" msgstr "" msgid "Yes" msgstr "" msgid "Concurrency Exception" msgstr "" msgid "This record has been modified while you were editing it." msgstr "" msgid "Cancel saving" msgstr "" msgid "Compare" msgstr "" msgid "See the modified version" msgstr "" msgid "Write Anyway" msgstr "" msgid "Save your current version" msgstr "" #, python-format msgid "Compare: %s" msgstr "" msgid "Close" msgstr "" msgid "Application Error" msgstr "" msgid "Report Bug" msgstr "" #, python-format msgid "Check URL: %s" msgstr "" msgid "Unable to check for new version." msgstr "" msgid "A new version is available!" msgstr "" msgid "Download" msgstr "" msgid "Could not get a session." msgstr "" msgid "Too many requests. Try again later." msgstr "" msgid "Not found." msgstr "" msgid "Not Found." msgstr "" msgid "..." msgstr "" msgid "Search..." msgstr "" msgid "Create..." msgstr "" #, python-format msgid "Create \"%s\"..." msgstr "" msgid "Value" msgstr "" msgid "Displayed value" msgstr "" msgid "Format" msgstr "" msgid "Display format" msgstr "" msgid "Open the calendar" msgstr "" msgid "Date Format" msgstr "" msgid "Displayed date format" msgstr "" msgid "y" msgstr "" msgid "True" msgstr "" msgid "t" msgstr "" msgid "False" msgstr "" msgid "Digits" msgstr "" msgid "The number of decimal" msgstr "" msgid "Template" msgstr "" msgid "Edit..." msgstr "" msgid "View Logs..." msgstr "" msgid "Attachments..." msgstr "" msgid "Notes..." msgstr "" msgid "Actions..." msgstr "" msgid "Relate..." msgstr "" msgid "Report..." msgstr "" msgid "Print..." msgstr "" msgid "E-Mail..." msgstr "" msgid "Bold" msgstr "" msgid "Italic" msgstr "" msgid "Underline" msgstr "" msgid "Align Left" msgstr "" msgid "Align Center" msgstr "" msgid "Align Right" msgstr "" msgid "Justify" msgstr "" msgid "Foreground Color" msgstr "" msgid "Select a color" msgstr "" msgid "Y" msgstr "" msgid "M" msgstr "" msgid "w" msgstr "" msgid "d" msgstr "" msgid "h" msgstr "" msgid "m" msgstr "" msgid "s" msgstr "" msgid "Preferences..." msgstr "" msgid "Toolbar" msgstr "" msgid "Default" msgstr "" msgid "Text and Icons" msgstr "" msgid "Text" msgstr "" msgid "Icons" msgstr "" msgid "Form" msgstr "" msgid "Save Column Width" msgstr "" msgid "Save Tree State" msgstr "" msgid "Spell Checking" msgstr "" msgid "Play Sound for Code Scanner" msgstr "" msgid "PDA Mode" msgstr "" msgid "Search Limit..." msgstr "" msgid "Check Version" msgstr "" msgid "Options" msgstr "" msgid "Documentation..." msgstr "" msgid "Keyboard Shortcuts..." msgstr "" msgid "About..." msgstr "" msgid "Help" msgstr "" msgid "No result found." msgstr "" msgid "Favorites" msgstr "" msgid "Manage..." msgstr "" msgid "Action" msgstr "" msgid "" "The following action requires to close all tabs.\n" "Do you want to continue?" msgstr "" msgid "Application Shortcuts" msgstr "" msgid "Global" msgstr "" msgid "Preferences" msgstr "" msgid "Search menu" msgstr "" msgid "Toggle menu" msgstr "" msgid "Previous tab" msgstr "" msgid "Next tab" msgstr "" msgid "Shortcuts" msgstr "" msgid "Quit" msgstr "" msgid "Edition Shortcuts" msgstr "" msgid "Text Entries" msgstr "" msgid "Cut selected text" msgstr "" msgid "Copy selected text" msgstr "" msgid "Paste copied text" msgstr "" msgid "Next entry" msgstr "" msgid "Previous entry" msgstr "" msgid "Relation Entries" msgstr "" msgid "Create new relation" msgstr "" msgid "Open/Search relation" msgstr "" msgid "List Entries" msgstr "" msgid "Switch view" msgstr "" msgid "Create/Select new line" msgstr "" msgid "Open relation" msgstr "" msgid "Mark line for deletion/removal" msgstr "" msgid "Mark line for removal" msgstr "" msgid "Unmark line for deletion" msgstr "" msgid "List/Tree Shortcuts" msgstr "" msgid "Move Cursor" msgstr "" msgid "Move right" msgstr "" msgid "Move left" msgstr "" msgid "Move up" msgstr "" msgid "Move down" msgstr "" msgid "Move up of one page" msgstr "" msgid "Move down of one page" msgstr "" msgid "Move to top" msgstr "" msgid "Move to bottom" msgstr "" msgid "Move to parent" msgstr "" msgid "Edition" msgstr "" msgid "Copy selected rows" msgstr "" msgid "Insert copied rows" msgstr "" msgid "Select all" msgstr "" msgid "Unselect all" msgstr "" msgid "Select parent" msgstr "" msgid "Select/Activate current row" msgstr "" msgid "Toggle selection" msgstr "" msgid "Expand/Collapse" msgstr "" msgid "Expand row" msgstr "" msgid "Collapse row" msgstr "" msgid "Toggle row" msgstr "" msgid "Collapse all rows" msgstr "" msgid "Expand all rows" msgstr "" msgid "Close Tab" msgstr "" msgid "modularity, scalability and security" msgstr "" msgid "translator-credits" msgstr "" #, python-format msgid "Attachments (%s)" msgstr "" msgid "Code Scanner" msgstr "" msgid "Code" msgstr "" msgid "Profile Editor" msgstr "" msgid "Profile" msgstr "" msgid "Add new profile" msgstr "" msgid "Remove selected profile" msgstr "" msgid "Host:" msgstr "" msgid "Database:" msgstr "" msgid "Fetching databases list" msgstr "" msgid "Username:" msgstr "" msgid "Incompatible version of the server." msgstr "" msgid "Could not connect to the server." msgstr "" msgid "Login" msgstr "" msgid "_Cancel" msgstr "" msgid "Cancel connection to the Tryton server" msgstr "" msgid "C_onnect" msgstr "" msgid "Connect the Tryton server" msgstr "" msgid "Profile:" msgstr "" msgid "Host / Database information" msgstr "" msgid "User name:" msgstr "" msgid "Unable to complete email entry" msgstr "" #, python-format msgid "E-mail %s" msgstr "" msgid "To:" msgstr "" msgid "Cc:" msgstr "" msgid "Bcc:" msgstr "" msgid "Subject:" msgstr "" msgid "Body" msgstr "" msgid "Reports" msgstr "" msgid "Attachments" msgstr "" msgid "Files" msgstr "" msgid "Send" msgstr "" msgid "Select File" msgstr "" msgid "Remove File" msgstr "" msgid "Select" msgstr "" msgid "Add..." msgstr "" msgid "Preview" msgstr "" msgid "Previous" msgstr "" msgid "Next" msgstr "" #, python-format msgid "Attachment (%s)" msgstr "" #, python-format msgid "Note (%d/%d)" msgstr "" msgid "You have to select one record." msgstr "" msgid "Are you sure to remove this record?" msgstr "" msgid "Are you sure to remove those records?" msgstr "" msgid "Records not removed." msgstr "" msgid "Records removed." msgstr "" msgid "Working now on the duplicated record(s)." msgstr "" msgid "Record saved." msgstr "" msgid "" "This record has been modified\n" "do you want to save it?" msgstr "" msgid "Launch action" msgstr "" msgid "Relate" msgstr "" msgid "Open related records" msgstr "" msgid "Report" msgstr "" msgid "Open report" msgstr "" msgid "Print" msgstr "" msgid "Print report" msgstr "" msgid "_Copy URL" msgstr "" msgid "Copy URL into clipboard" msgstr "" msgid "Unknown" msgstr "" msgid "Limit" msgstr "" msgid "Search Limit Settings" msgstr "" msgid "Limit:" msgstr "" #, python-format msgid "Logs (%s)" msgstr "" msgid "Model:" msgstr "" msgid "ID:" msgstr "" msgid "Created by:" msgstr "" msgid "Created at:" msgstr "" msgid "Last Modified by:" msgstr "" msgid "Last Modified at:" msgstr "" #, python-format msgid "Notes (%s)" msgstr "" msgid "Edit User Preferences" msgstr "" msgid "Preference" msgstr "" msgid "Revision" msgstr "" msgid "Select a revision" msgstr "" msgid "Revision:" msgstr "" msgid "_Switch View" msgstr "" msgid "Switch View" msgstr "" msgid "_Previous" msgstr "" msgid "Previous Record" msgstr "" msgid "_Next" msgstr "" msgid "Next Record" msgstr "" msgid "_Search" msgstr "" msgid "_New" msgstr "" msgid "Create a new record" msgstr "" msgid "_Save" msgstr "" msgid "Save this record" msgstr "" msgid "_Reload/Undo" msgstr "" msgid "Reload/Undo" msgstr "" msgid "_Duplicate" msgstr "" msgid "_Delete..." msgstr "" msgid "View _Logs..." msgstr "" msgid "Show revisions..." msgstr "" msgid "A_ttachments..." msgstr "" msgid "Add an attachment to the record" msgstr "" msgid "_Notes..." msgstr "" msgid "Add a note to the record" msgstr "" msgid "_Actions..." msgstr "" msgid "_Relate..." msgstr "" msgid "_Report..." msgstr "" msgid "_Print..." msgstr "" msgid "_E-Mail..." msgstr "" msgid "Send an e-mail using the record" msgstr "" msgid "_Export Data..." msgstr "" msgid "_Import Data..." msgstr "" msgid "Copy _URL..." msgstr "" msgid "_Close Tab" msgstr "" msgid "All fields" msgstr "" msgid "_Add" msgstr "" msgid "_Remove" msgstr "" msgid "_Clear" msgstr "" msgid "Fields selected" msgstr "" msgid "CSV Parameters" msgstr "" msgid "Delimiter:" msgstr "" msgid "Quote char:" msgstr "" msgid "Encoding:" msgstr "" msgid "Use locale format" msgstr "" msgid "Field name" msgstr "" #, python-format msgid "CSV Export: %s" msgstr "" msgid "_Save Export" msgstr "" msgid "_URL Export" msgstr "" msgid "_Delete Export" msgstr "" msgid "Predefined exports" msgstr "" msgid "Name" msgstr "" msgid "Open" msgstr "" msgid "Save" msgstr "" msgid "Listed Records" msgstr "" msgid "Selected Records" msgstr "" msgid "Ignore search limit" msgstr "" msgid "Add field names" msgstr "" #, python-format msgid "%s (string)" msgstr "" #, python-format msgid "%s (model name)" msgstr "" #, python-format msgid "%s/Record Name" msgstr "" msgid "What is the name of this export?" msgstr "" #, python-format msgid "Override '%s' definition?" msgstr "" #, python-format msgid "%d record saved." msgstr "" #, python-format msgid "%d records saved." msgstr "" msgid "Export failed" msgstr "" msgid "Link" msgstr "" msgid "Delete" msgstr "" msgid "Discard changes" msgstr "" msgid "Save and New" msgstr "" msgid "Add and New" msgstr "" msgid "Add" msgstr "" msgid "Apply changes" msgstr "" msgid "Switch" msgstr "" msgid "Remove " msgstr "" msgid "Create a new record " msgstr "" msgid "Delete selected record " msgstr "" msgid "Undelete selected record " msgstr "" #, python-format msgid "CSV Import: %s" msgstr "" msgid "_Auto-Detect" msgstr "" msgid "File to Import:" msgstr "" msgid "Open..." msgstr "" msgid "Lines to Skip:" msgstr "" msgid "You must select an import file first." msgstr "" msgid "Detection failed" msgstr "" #, python-format msgid "Unknown column header \"%s\"" msgstr "" msgid "Error" msgstr "" msgid "Import failed" msgstr "" #, python-format msgid "%d record imported." msgstr "" #, python-format msgid "%d records imported." msgstr "" msgid "Search" msgstr "" msgid "New" msgstr "" #, python-format msgid "Search %s" msgstr "" msgid "Wizard" msgstr "" msgid "ID" msgstr "" msgid "Created by" msgstr "" msgid "Created at" msgstr "" msgid "Edited by" msgstr "" msgid "Edited at" msgstr "" msgid "Unable to set view tree state" msgstr "" #, python-format msgid "\"%s\" is not valid according to its domain." msgstr "" #, python-format msgid "\"%s\" is required." msgstr "" #, python-format msgid "The values of \"%s\" are not valid." msgstr "" msgid "Pre-validation" msgstr "" msgid ":" msgstr "" msgid "Scan" msgstr "" msgid "Image Size" msgstr "" msgid "Width:" msgstr "" msgid "Height:" msgstr "" msgid "PNG image (*.png)" msgstr "" msgid "Save As" msgstr "" msgid "Image size too large." msgstr "" msgid "Copy" msgstr "" msgid "Paste" msgstr "" msgid ".." msgstr "" msgid "Open filters" msgstr "" msgid "Show bookmarks of filters" msgstr "" msgid "Remove this bookmark" msgstr "" msgid "Bookmark this filter" msgstr "" msgid "Show active records" msgstr "" msgid "Show inactive records" msgstr "" msgid "Bookmark Name:" msgstr "" msgid "Find" msgstr "" msgid "Today" msgstr "" msgid "go back" msgstr "" msgid "go forward" msgstr "" msgid "previous year" msgstr "" msgid "next year" msgstr "" msgid "Day" msgstr "" msgid "Week" msgstr "" msgid "Month" msgstr "" msgid "Select..." msgstr "" msgid "Clear" msgstr "" msgid "All files" msgstr "" msgid "Show plain text" msgstr "" msgid "Add value" msgstr "" #, python-format msgid "Remove \"%s\"" msgstr "" msgid "Images" msgstr "" msgid "Add existing record" msgstr "" msgid "Remove selected record" msgstr "" msgid "Open the record " msgstr "" msgid "Clear the field " msgstr "" msgid "Search a record " msgstr "" msgid "Edit selected record" msgstr "" msgid "Delete selected record" msgstr "" #, python-format msgid "%s%%" msgstr "" msgid "Choose a language" msgstr "" msgid "Translation" msgstr "" msgid "Edit" msgstr "" msgid "Fuzzy" msgstr "" msgid "You need to save the record before adding translations." msgstr "" msgid "No other language available." msgstr "" msgid "#ERROR" msgstr "" msgid "Translate view" msgstr "" ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1745680285.1274436 tryton-7.0.24/tryton/data/locale/uk/0000755000175000017500000000000015003173635015404 5ustar00cedced././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1745680285.167444 tryton-7.0.24/tryton/data/locale/uk/LC_MESSAGES/0000755000175000017500000000000015003173635017171 5ustar00cedced././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1745680279.0 tryton-7.0.24/tryton/data/locale/uk/LC_MESSAGES/tryton.mo0000644000175000017500000005466415003173627021105 0ustar00cedcedm **>Odv~ 1AJ Q\`y      (:#P%t  ,;D&K r     'AF ^k ~    % 7 AMU\s   "8M U _i {     #(/5<DIeknrx  # &5 :G[j pz |      ! - 5 I N R W c n w z       ! ! !2!G!O!d!l!!!!!! ! !!!!!" "+" 1">"G"O"^"g" l" x"""" "" " " " ""#.#C# J# U#_#g# p#z## ###### ##$$ +$7$P$ W$c$r$ $ $$ $$$ $$$ % %4%N%d%t%%%% % %%% %%I%1&!G&5i&8&&& & &&# '.'6' E'Q' V'w'''' ''' (#( 5( @(J( P(^( c((((( ((((%(7)K) [)g) l)y)) ) )) ) ) ))))) ) * * * !*.* 6*A* G*T* \* i*u*w** ** **$* * ***3+<Q++ ++++++Y-,.%@.!f.).%.... ////// /7/?V///'//00 &0 00/=01m0(0"0&0101$@1!e1#11@1122NQ2N223323H3_3 x33$333334&4F94'4 44445585G5"e55554516561H6z606A6&7+/7)[757777 8.#8R8g8}88,8289#$9 H9%S9.y9'9)99:$:5:>J:0::::?;@;T;&c;;%;;;<1<B<S< s< ~<*< < <<< <="= 2=@=C= G=4T=!=5===/> D>$Q>$v>>>">#> ? ?B:?}? ????7?7@ =@%K@q@>@%@'@(A(GA*pA!A@AA BB$BBB`BB,BB#BBC%C 9CFCWC/wCCC!C D#D+7DcD5vDD DD4D%1EWEpEEEEEE! F-F6F RF^F!mFF FFF#FG'G9GNGmG|GGG0G,G&!HHH*QH |H HHHHHH,H$I"DI5gI I I8I"IJ/J<CJJJJJJJK-K??KK KKIK!L,%L2RL0L.L"L%M .M8MOMfM M MMMfM4>N'sNYNWN MOWO,hO!O!OTO%.PTPtP PSP6PAQ=_Q4QQQ9Q&6R>]RRRR%RS2"S UScSWrS SSS7SI-T\wTT T TUU2UDUcU {UUUUUU VV3VHV ^VkVVV VV V VVWWW :W EWRW6UWWJWWWXSXUgXWX0Y.FYuY5xYYY"%s" is not valid according to its domain."%s" is required.%d record imported.%d record saved.%d records imported.%d records saved.%s (%s)%s (model name)%s (string)%s%%, ,........:All fieldsFields selectedPredefined exportsCreate...Search...A new version is available!A_ttachments...About...ActionActions...AddAdd a note to the recordAdd an attachment to the recordAdd existing recordAdd field namesAdd new profileAdd valueAdd...Align CenterAlign LeftAlign RightAll filesAlways ignore this warning.Application ErrorApplication ShortcutsAre you sure to remove this record?Are you sure to remove those records?Attachment (%s)AttachmentsAttachments (%s)Attachments...Bcc:BodyBoldBookmark Name:Bookmark this filterBy: CSV Export: %sCSV Import: %sCSV ParametersC_onnectCancelCancel connection to the Tryton serverCancel savingCc:Check URL: %sCheck VersionChoose a languageClearClear the field CloseClose TabCollapse all rowsCollapse rowCompareCompare: %sConcurrency ExceptionConnect the Tryton serverCopyCopy URL into clipboardCopy _URL...Copy selected textCould not connect to the server.Create a new recordCreate a new record Create new relationCreate/Select new lineCreated atCreated at:Created byCreated by:Cut selected textDatabase:Date FormatDefaultDeleteDelete selected recordDelete selected record Delimiter:Detection failedDigitsDisplay formatDisplayed date formatDisplayed valueDo you want to proceed?DownloadE-Mail...E-mail %sEditEdit User PreferencesEdit selected recordEdit...Edited atEdited byEdition ShortcutsEncoding:ErrorExpand all rowsExpand rowExpand/CollapseExport failedFalseFavoritesFetching databases listField nameFile to Import:FilesFindForeground ColorFormFormatFuzzyGlobalHeight:HelpHost / Database informationHost:IDID:IconsIgnore search limitImage SizeImage size too large.ImagesImport failedIncompatible version of the server.ItalicJustifyKeyboard Shortcuts...Launch actionLimitLimit:Lines to Skip:LinkList EntriesList/Tree ShortcutsListed RecordsLoginLogs (%s)MManage...Mark line for deletion/removalMark line for removalModel:Move CursorMove downMove down of one pageMove leftMove rightMove to bottomMove to parentMove to topMove upMove up of one pageNameNewNextNext RecordNext entryNext tabNoNo action defined.No other language available.No result found.Note (%d/%d)Notes (%s)Notes...OKOpenOpen filtersOpen related recordsOpen relationOpen reportOpen the calendarOpen the record Open...Open/Search relationOptionsOverride '%s' definition?PDA ModePNG image (*.png)PastePaste copied textPre-validationPreferencePreferencesPreferences...PreviewPreviousPrevious RecordPrevious entryPrevious tabPrintPrint reportPrint...ProfileProfile EditorProfile:QuitQuote char:Record saved.Records not removed.Records removed.RelateRelate...Relation EntriesReload/UndoRemove "%s"Remove Remove FileRemove selected profileRemove selected recordRemove this bookmarkReportReport BugReport...ReportsRevisionRevision:SaveSave AsSave As...Save Column WidthSave Tree StateSave this recordSave your current versionSearchSearch %sSearch Limit SettingsSearch Limit...Search a record Search menuSee the modified versionSelectSelect FileSelect a colorSelect a revisionSelect allSelect parentSelect your actionSelect...Select/Activate current rowSelected RecordsSelectionSendSend an e-mail using the recordShortcutsShow active recordsShow bookmarks of filtersShow inactive recordsShow plain textShow revisions...Spell CheckingSubject:SwitchSwitch ViewSwitch viewTemplateTextText EntriesText and IconsThe following action requires to close all tabs. Do you want to continue?The number of decimalThe values of "%s" are not valid.This record has been modified do you want to save it?This record has been modified while you were editing it.To:TodayToggle menuToggle rowToggle selectionToo many requests. Try again later.ToolbarTranslate viewTranslationTrueUnable to check for new version.Unable to complete email entryUnable to set locale %sUnable to set view tree stateUndelete selected record UnderlineUnknownUnmark line for deletionUnselect allUse locale formatUser name:Username:ValueView _Logs...WeekWhat is the name of this export?Width:WizardWorking now on the duplicated record(s).Write AnywayYYesYou have to select one record.You must select an import file first.You need to save the record before adding translations.Your selection:_Actions..._Add_Auto-Detect_Cancel_Clear_Close Tab_Copy URL_Delete Export_Delete..._Duplicate_E-Mail..._Export Data..._Import Data..._New_Next_Notes..._Previous_Print..._Relate..._Reload/Undo_Remove_Report..._Save_Save Export_Search_Switch View_URL Exportddevelopment modego backgo forwardhlogging everything at INFO levelmmodularity, scalability and securitynext yearprevious yearsspecify alternate config filespecify the file used to output logging informationspecify the log level: DEBUG, INFO, WARNING, ERROR, CRITICALspecify the login userspecify the server hostname:portttranslator-creditswyProject-Id-Version: PROJECT VERSION Report-Msgid-Bugs-To: EMAIL@ADDRESS POT-Creation-Date: 2025-04-26 17:11+0200 PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE Last-Translator: FULL NAME Language: uk Language-Team: uk Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2); MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: 8bit Generated-By: Babel 2.17.0 Значення "%s" недійсне відповідно до свого домену.Значення "%s" обов'язкове.%d запис імпортовано.%d запис збережено.%d записів імпортовано.%d записів збережено.%s (%s)%s (ім'я моделі)%s (рядок)%s%%, ,........:Всі поляВибрані поляПопередньо визначені експортиСтворити...Пошук...Доступна нова версія!Вкладення...Про програму...ДіяДії...ДодатиДодати примітку до записуДодати вкладення до записуДодати існуючий записДодати імена полівДодати новий профільДодати значенняДодати...Вирівняти по центруВирівняти ліворучВирівняти праворучВсі файлиЗавжди ігнорувати це попередження.Помилка програмиКомбінації клавіш програмиВи впевнені, що бажаєте видалити цей запис?Ви впевнені, що бажаєте видалити ці записи?Вкладення (%s)ВкладенняВкладення (%s)Вкладення...Сліпа копія:ПовідомленняЖирнийІм'я закладки:Зберегти цей фільтрВід: Експорт CSV: %sІмпорт CSV: %sПараметри CSV_ПідключитиСкасуватиСкасування підключення до сервера TrytonСкасувати збереженняКопія:Перевірка URL: %sПеревірка версіїВибрати мовуОчиститиОчистити поле ЗакритиЗакрити вкладкуЗгорнути всі рядкиЗгорнути рядокПорівнятиПорівняти: %sВиняток одночасного доступуПідключення до сервера TrytonКопіюватиКопіювати URL в буфер обмінуКопіювати _URL...Копіювати виділений текстНе вдалося підключитися до сервера.Створити новий записСтворити новий запис Створити новий зв'язокСтворити/Вибрати новий рядокКоли створенноКоли створено:Ким створеноКим створено:Вирізати виділений текстБаза даних:Формат датиЗа замовчуваннямВидалитиВидалити вибраний записВидалити вибраний запис Роздільник:Помилка визначенняЦифриФормат відображенняВідображений формат датиВідображене значенняВи бажаєте продовжити?ЗавантажитиЕл.пошта...Ел.лист %sРедагуватиЗмінити налаштування користувачаРедагувати вибраний записРедагувати...Коли зміненоКим зміненоКомбінації клавіш для редагуванняКодування:ПомилкаРозгорнути всі рядкиРозгорнути рядокРозгорнути/ЗгорнутиПомилка експортуНеправдаВибранеОтримання списку баз данихІм'я поляФайл для імпорту:ФайлиЗнайтиКолір переднього плануФормаФорматНечіткийГлобальніВисота:ДовідкаСервер / База данихСервер:IDID:ЗначкиІгнорувати обмеження пошукуРозмір зображенняРозмір зображення завеликий.ЗображенняПомилка імпортуНесумісна версія сервера.КурсивВирівняти по шириніКомбінації клавіш...Виконати діюОбмеженняКількість записів:Рядки для пропуску:ПосиланняЗаписи спискуКомбінації клавіш для Списку/ДереваЗаписи спискуЛогінколоди (%s)МКерування...Позначити рядок для видаленняПозначити рядок для видаленняМодель:Переміщення курсораПеремістити внизПеремістити вниз на одну сторінкуПеремістити ліворучПеремістити праворучПеремістити на кінецьПеремістити до батькаПеремістити на початокПеремістити вгоруПеремістити вгору на одну сторінкуІм'яНовийНаступнийНаступний записНаступний записНаступна вкладкаНіНіяких дій не визначено.Немає іншої мови.Нічого не знайдено.Примітка (%d/%d)Примітки (%s)Примітки...ГараздВідкритиВідкрити фільтриВідкрити пов'язані записиВідкрити зв'язокВідкрити звітВідкрити календарВідкрити запис Відкрити...Відкрити/Шукати зв'язокПараметриПеревизначити визначення '%s'?Спрощений режимЗображення PNG (*.png)ВставитиВставити скопійований текстПопередня перевіркаНалаштуванняНалаштуванняНалаштування...ПереглядПопереднійПопередній записПопередній записПопередня вкладкаДрукДрукувати звітДрук...ПрофільРедактор профілівПрофіль:ВихідСимвол цитати:Запис збережено.Записи не видалено.Записи видалено.Пов'язаніПов'язані...Пов'язані записиОновитиВидалити "%s"Видалити Видалити файлВидалити вибраний профільВидалити вибраний записВидалити цю закладкуЗвітПовідомити про помилкуЗвіт...ЗвітиРевізіяРевізія:ЗберегтиЗберегти якЗберегти як...Зберегти ширину колонокЗберегти стан деревЗберегти цей записЗберегти вашу поточну версіюПошукПошук %sНалаштування обмеження пошукуОбмеження пошуку...Пошук запису Пошук менюПереглянути модифіковану версіюВибратиВибрати файлВиберіть колірВиберіть ревізіюВибрати всіВибрати батькаВиберіть діюВибрати...Вибрати/Активувати поточний рядокВибрані записиВибірНадіслатиНадіслати ел.лист, використовуючи записКомбінації клавішПоказати активні записиПоказати збережені фільтриПоказати неактивні записиПоказати звичайний текстПоказати ревізії...Перевірка правописуТема:ПереключитиПереключитиПереключити видШаблонТекстТекстові записиТекст та ЗначкиНаступна дія вимагає закриття всіх вкладок. Продовжити?Кількість десяткових знаківЗначення "%s" недійсні.Цей запис було змінено, ви бажаєте зберегти його?Цей запис було змінено, поки ви його редагували.Кому:СьогодніПоказати/Приховати менюПереключити рядокПереключити вибірДуже багато запитів. Спробуйте ще раз пізніше.Панель інструментівПереклад вкладкиПерекладПравдаНеможливо перевірити наявність нової версії.Не вдалося відправити ел.листНеможливо встановити локалізацію %sНеможливо встановити стан дереваВідновити вибраний запис ПідкресленийНевідомоЗняти позначку видалення рядкаСкасувати вибір всіхВикористовувати локальний форматІм'я користувача:Ім'я користувача:ЗначенняПерегляд _журналів...ТижденьЯк називається цей експорт?Ширина:МайстерЗараз працюємо з дубльованим записом(записами).Записати все одноРТакВи повинні вибрати один запис.Спочатку потрібно вибрати файл імпорту.Треба зберегти запис перед додаванням перекладів.Ваш вибір:_Дії..._Додати_Автовизначення_Скасувати_Очистити_Закрити вкладку_Копіювати URL_Видалити експорт_Видалити..._Копіювати_Ел.пошта..._Експорт даних..._Імпорт даних..._Новий_Наступний_Примітки..._Попередній_Друк..._Пов'язані..._Оновити_Видалити_Звіт..._Зберегти_Зберегти експорт_Пошук_Переключити_Експорт URLдрежим розробкиназадвпередгреєстрація всього на рівні INFOхмодульність, масштабованість та безпеканаступний рікпопередній ріксвкажіть альтернативний конфігураційний файлвкажіть файл для виведення інформації журналувкажіть рівень реєстрації: DEBUG, INFO, WARNING, ERROR, CRITICALвкажіть логін користувачавкажіть ім'я сервера:портпВіктор Бєлан Олександр Бєлантт././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1703673879.0 tryton-7.0.24/tryton/data/locale/uk/LC_MESSAGES/tryton.po0000644000175000017500000006072414543000027021071 0ustar00cedced# msgid "" msgstr "Content-Type: text/plain; charset=utf-8\n" msgid "specify alternate config file" msgstr "вкажіть альтернативний конфігураційний файл" msgid "development mode" msgstr "режим розробки" msgid "logging everything at INFO level" msgstr "реєстрація всього на рівні INFO" msgid "specify the log level: DEBUG, INFO, WARNING, ERROR, CRITICAL" msgstr "вкажіть рівень реєстрації: DEBUG, INFO, WARNING, ERROR, CRITICAL" msgid "specify the file used to output logging information" msgstr "вкажіть файл для виведення інформації журналу" msgid "specify the login user" msgstr "вкажіть логін користувача" msgid "specify the server hostname:port" msgstr "вкажіть ім'я сервера:порт" msgid "disable thread usage" msgstr "" #, python-format msgid "Unable to set locale %s" msgstr "Неможливо встановити локалізацію %s" msgid ", " msgstr ", " msgid ",..." msgstr ",..." #, python-format msgid "%s (%s)" msgstr "%s (%s)" msgid "Select your action" msgstr "Виберіть дію" msgid "No action defined." msgstr "Ніяких дій не визначено." msgid "By: " msgstr "Від: " msgid "Selection" msgstr "Вибір" msgid "Cancel" msgstr "Скасувати" msgid "OK" msgstr "Гаразд" msgid "Your selection:" msgstr "Ваш вибір:" msgid "Save As..." msgstr "Зберегти як..." msgid "Do you want to proceed?" msgstr "Ви бажаєте продовжити?" msgid "Always ignore this warning." msgstr "Завжди ігнорувати це попередження." msgid "No" msgstr "Ні" msgid "Yes" msgstr "Так" msgid "Concurrency Exception" msgstr "Виняток одночасного доступу" msgid "This record has been modified while you were editing it." msgstr "Цей запис було змінено, поки ви його редагували." msgid "Cancel saving" msgstr "Скасувати збереження" msgid "Compare" msgstr "Порівняти" msgid "See the modified version" msgstr "Переглянути модифіковану версію" msgid "Write Anyway" msgstr "Записати все одно" msgid "Save your current version" msgstr "Зберегти вашу поточну версію" #, python-format msgid "Compare: %s" msgstr "Порівняти: %s" msgid "Close" msgstr "Закрити" msgid "Application Error" msgstr "Помилка програми" msgid "Report Bug" msgstr "Повідомити про помилку" #, python-format msgid "Check URL: %s" msgstr "Перевірка URL: %s" msgid "Unable to check for new version." msgstr "Неможливо перевірити наявність нової версії." msgid "A new version is available!" msgstr "Доступна нова версія!" msgid "Download" msgstr "Завантажити" #, fuzzy msgid "Could not get a session." msgstr "Не вдалося підключитися до сервера." msgid "Too many requests. Try again later." msgstr "Дуже багато запитів. Спробуйте ще раз пізніше." #, fuzzy msgid "Not found." msgstr "Нічого не знайдено." #, fuzzy msgid "Not Found." msgstr "Нічого не знайдено." msgid "..." msgstr "..." msgid "Search..." msgstr "Пошук..." msgid "Create..." msgstr "Створити..." #, fuzzy, python-format msgid "Create \"%s\"..." msgstr "Створити \"%s\"..." msgid "Value" msgstr "Значення" msgid "Displayed value" msgstr "Відображене значення" msgid "Format" msgstr "Формат" msgid "Display format" msgstr "Формат відображення" msgid "Open the calendar" msgstr "Відкрити календар" msgid "Date Format" msgstr "Формат дати" msgid "Displayed date format" msgstr "Відображений формат дати" msgid "y" msgstr "т" msgid "True" msgstr "Правда" msgid "t" msgstr "п" msgid "False" msgstr "Неправда" msgid "Digits" msgstr "Цифри" msgid "The number of decimal" msgstr "Кількість десяткових знаків" msgid "Template" msgstr "Шаблон" msgid "Edit..." msgstr "Редагувати..." #, fuzzy msgid "View Logs..." msgstr "Перегляд _журналів..." msgid "Attachments..." msgstr "Вкладення..." msgid "Notes..." msgstr "Примітки..." msgid "Actions..." msgstr "Дії..." msgid "Relate..." msgstr "Пов'язані..." msgid "Report..." msgstr "Звіт..." msgid "Print..." msgstr "Друк..." msgid "E-Mail..." msgstr "Ел.пошта..." msgid "Bold" msgstr "Жирний" msgid "Italic" msgstr "Курсив" msgid "Underline" msgstr "Підкреслений" msgid "Align Left" msgstr "Вирівняти ліворуч" msgid "Align Center" msgstr "Вирівняти по центру" msgid "Align Right" msgstr "Вирівняти праворуч" msgid "Justify" msgstr "Вирівняти по ширині" msgid "Foreground Color" msgstr "Колір переднього плану" msgid "Select a color" msgstr "Виберіть колір" msgid "Y" msgstr "Р" msgid "M" msgstr "М" msgid "w" msgstr "т" msgid "d" msgstr "д" msgid "h" msgstr "г" msgid "m" msgstr "х" msgid "s" msgstr "с" msgid "Preferences..." msgstr "Налаштування..." msgid "Toolbar" msgstr "Панель інструментів" msgid "Default" msgstr "За замовчуванням" msgid "Text and Icons" msgstr "Текст та Значки" msgid "Text" msgstr "Текст" msgid "Icons" msgstr "Значки" msgid "Form" msgstr "Форма" msgid "Save Column Width" msgstr "Зберегти ширину колонок" msgid "Save Tree State" msgstr "Зберегти стан дерев" msgid "Spell Checking" msgstr "Перевірка правопису" msgid "Play Sound for Code Scanner" msgstr "" msgid "PDA Mode" msgstr "Спрощений режим" msgid "Search Limit..." msgstr "Обмеження пошуку..." msgid "Check Version" msgstr "Перевірка версії" msgid "Options" msgstr "Параметри" #, fuzzy msgid "Documentation..." msgstr "Дії..." msgid "Keyboard Shortcuts..." msgstr "Комбінації клавіш..." msgid "About..." msgstr "Про програму..." msgid "Help" msgstr "Довідка" msgid "No result found." msgstr "Нічого не знайдено." msgid "Favorites" msgstr "Вибране" msgid "Manage..." msgstr "Керування..." msgid "Action" msgstr "Дія" msgid "" "The following action requires to close all tabs.\n" "Do you want to continue?" msgstr "" "Наступна дія вимагає закриття всіх вкладок.\n" "Продовжити?" msgid "Application Shortcuts" msgstr "Комбінації клавіш програми" msgid "Global" msgstr "Глобальні" msgid "Preferences" msgstr "Налаштування" msgid "Search menu" msgstr "Пошук меню" msgid "Toggle menu" msgstr "Показати/Приховати меню" msgid "Previous tab" msgstr "Попередня вкладка" msgid "Next tab" msgstr "Наступна вкладка" msgid "Shortcuts" msgstr "Комбінації клавіш" msgid "Quit" msgstr "Вихід" msgid "Edition Shortcuts" msgstr "Комбінації клавіш для редагування" msgid "Text Entries" msgstr "Текстові записи" msgid "Cut selected text" msgstr "Вирізати виділений текст" msgid "Copy selected text" msgstr "Копіювати виділений текст" msgid "Paste copied text" msgstr "Вставити скопійований текст" msgid "Next entry" msgstr "Наступний запис" msgid "Previous entry" msgstr "Попередній запис" msgid "Relation Entries" msgstr "Пов'язані записи" msgid "Create new relation" msgstr "Створити новий зв'язок" msgid "Open/Search relation" msgstr "Відкрити/Шукати зв'язок" msgid "List Entries" msgstr "Записи списку" msgid "Switch view" msgstr "Переключити вид" msgid "Create/Select new line" msgstr "Створити/Вибрати новий рядок" msgid "Open relation" msgstr "Відкрити зв'язок" msgid "Mark line for deletion/removal" msgstr "Позначити рядок для видалення" msgid "Mark line for removal" msgstr "Позначити рядок для видалення" msgid "Unmark line for deletion" msgstr "Зняти позначку видалення рядка" msgid "List/Tree Shortcuts" msgstr "Комбінації клавіш для Списку/Дерева" msgid "Move Cursor" msgstr "Переміщення курсора" msgid "Move right" msgstr "Перемістити праворуч" msgid "Move left" msgstr "Перемістити ліворуч" msgid "Move up" msgstr "Перемістити вгору" msgid "Move down" msgstr "Перемістити вниз" msgid "Move up of one page" msgstr "Перемістити вгору на одну сторінку" msgid "Move down of one page" msgstr "Перемістити вниз на одну сторінку" msgid "Move to top" msgstr "Перемістити на початок" msgid "Move to bottom" msgstr "Перемістити на кінець" msgid "Move to parent" msgstr "Перемістити до батька" #, fuzzy msgid "Edition" msgstr "Редагувати" #, fuzzy msgid "Copy selected rows" msgstr "Копіювати виділений текст" msgid "Insert copied rows" msgstr "" msgid "Select all" msgstr "Вибрати всі" msgid "Unselect all" msgstr "Скасувати вибір всіх" msgid "Select parent" msgstr "Вибрати батька" msgid "Select/Activate current row" msgstr "Вибрати/Активувати поточний рядок" msgid "Toggle selection" msgstr "Переключити вибір" msgid "Expand/Collapse" msgstr "Розгорнути/Згорнути" msgid "Expand row" msgstr "Розгорнути рядок" msgid "Collapse row" msgstr "Згорнути рядок" msgid "Toggle row" msgstr "Переключити рядок" msgid "Collapse all rows" msgstr "Згорнути всі рядки" msgid "Expand all rows" msgstr "Розгорнути всі рядки" msgid "Close Tab" msgstr "Закрити вкладку" msgid "modularity, scalability and security" msgstr "модульність, масштабованість та безпека" msgid "translator-credits" msgstr "" "Віктор Бєлан\n" "Олександр Бєлан" #, python-format msgid "Attachments (%s)" msgstr "Вкладення (%s)" msgid "Code Scanner" msgstr "" msgid "Code" msgstr "" msgid "Profile Editor" msgstr "Редактор профілів" msgid "Profile" msgstr "Профіль" msgid "Add new profile" msgstr "Додати новий профіль" msgid "Remove selected profile" msgstr "Видалити вибраний профіль" msgid "Host:" msgstr "Сервер:" msgid "Database:" msgstr "База даних:" msgid "Fetching databases list" msgstr "Отримання списку баз даних" msgid "Username:" msgstr "Ім'я користувача:" msgid "Incompatible version of the server." msgstr "Несумісна версія сервера." msgid "Could not connect to the server." msgstr "Не вдалося підключитися до сервера." msgid "Login" msgstr "Логін" msgid "_Cancel" msgstr "_Скасувати" msgid "Cancel connection to the Tryton server" msgstr "Скасування підключення до сервера Tryton" msgid "C_onnect" msgstr "_Підключити" msgid "Connect the Tryton server" msgstr "Підключення до сервера Tryton" msgid "Profile:" msgstr "Профіль:" msgid "Host / Database information" msgstr "Сервер / База даних" msgid "User name:" msgstr "Ім'я користувача:" msgid "Unable to complete email entry" msgstr "Не вдалося відправити ел.лист" #, python-format msgid "E-mail %s" msgstr "Ел.лист %s" msgid "To:" msgstr "Кому:" msgid "Cc:" msgstr "Копія:" msgid "Bcc:" msgstr "Сліпа копія:" msgid "Subject:" msgstr "Тема:" msgid "Body" msgstr "Повідомлення" msgid "Reports" msgstr "Звіти" msgid "Attachments" msgstr "Вкладення" msgid "Files" msgstr "Файли" msgid "Send" msgstr "Надіслати" msgid "Select File" msgstr "Вибрати файл" msgid "Remove File" msgstr "Видалити файл" msgid "Select" msgstr "Вибрати" msgid "Add..." msgstr "Додати..." msgid "Preview" msgstr "Перегляд" msgid "Previous" msgstr "Попередній" msgid "Next" msgstr "Наступний" #, python-format msgid "Attachment (%s)" msgstr "Вкладення (%s)" #, python-format msgid "Note (%d/%d)" msgstr "Примітка (%d/%d)" msgid "You have to select one record." msgstr "Ви повинні вибрати один запис." msgid "Are you sure to remove this record?" msgstr "Ви впевнені, що бажаєте видалити цей запис?" msgid "Are you sure to remove those records?" msgstr "Ви впевнені, що бажаєте видалити ці записи?" msgid "Records not removed." msgstr "Записи не видалено." msgid "Records removed." msgstr "Записи видалено." msgid "Working now on the duplicated record(s)." msgstr "Зараз працюємо з дубльованим записом(записами)." msgid "Record saved." msgstr "Запис збережено." msgid "" "This record has been modified\n" "do you want to save it?" msgstr "" "Цей запис було змінено,\n" "ви бажаєте зберегти його?" msgid "Launch action" msgstr "Виконати дію" msgid "Relate" msgstr "Пов'язані" msgid "Open related records" msgstr "Відкрити пов'язані записи" msgid "Report" msgstr "Звіт" msgid "Open report" msgstr "Відкрити звіт" msgid "Print" msgstr "Друк" msgid "Print report" msgstr "Друкувати звіт" msgid "_Copy URL" msgstr "_Копіювати URL" msgid "Copy URL into clipboard" msgstr "Копіювати URL в буфер обміну" msgid "Unknown" msgstr "Невідомо" msgid "Limit" msgstr "Обмеження" msgid "Search Limit Settings" msgstr "Налаштування обмеження пошуку" msgid "Limit:" msgstr "Кількість записів:" #, python-format msgid "Logs (%s)" msgstr "колоди (%s)" msgid "Model:" msgstr "Модель:" msgid "ID:" msgstr "ID:" msgid "Created by:" msgstr "Ким створено:" msgid "Created at:" msgstr "Коли створено:" msgid "Last Modified by:" msgstr "" msgid "Last Modified at:" msgstr "" #, python-format msgid "Notes (%s)" msgstr "Примітки (%s)" msgid "Edit User Preferences" msgstr "Змінити налаштування користувача" msgid "Preference" msgstr "Налаштування" msgid "Revision" msgstr "Ревізія" msgid "Select a revision" msgstr "Виберіть ревізію" msgid "Revision:" msgstr "Ревізія:" msgid "_Switch View" msgstr "_Переключити" msgid "Switch View" msgstr "Переключити" msgid "_Previous" msgstr "_Попередній" msgid "Previous Record" msgstr "Попередній запис" msgid "_Next" msgstr "_Наступний" msgid "Next Record" msgstr "Наступний запис" msgid "_Search" msgstr "_Пошук" msgid "_New" msgstr "_Новий" msgid "Create a new record" msgstr "Створити новий запис" msgid "_Save" msgstr "_Зберегти" msgid "Save this record" msgstr "Зберегти цей запис" msgid "_Reload/Undo" msgstr "_Оновити" msgid "Reload/Undo" msgstr "Оновити" msgid "_Duplicate" msgstr "_Копіювати" msgid "_Delete..." msgstr "_Видалити..." msgid "View _Logs..." msgstr "Перегляд _журналів..." msgid "Show revisions..." msgstr "Показати ревізії..." msgid "A_ttachments..." msgstr "Вкладення..." msgid "Add an attachment to the record" msgstr "Додати вкладення до запису" msgid "_Notes..." msgstr "_Примітки..." msgid "Add a note to the record" msgstr "Додати примітку до запису" msgid "_Actions..." msgstr "_Дії..." msgid "_Relate..." msgstr "_Пов'язані..." msgid "_Report..." msgstr "_Звіт..." msgid "_Print..." msgstr "_Друк..." msgid "_E-Mail..." msgstr "_Ел.пошта..." msgid "Send an e-mail using the record" msgstr "Надіслати ел.лист, використовуючи запис" msgid "_Export Data..." msgstr "_Експорт даних..." msgid "_Import Data..." msgstr "_Імпорт даних..." msgid "Copy _URL..." msgstr "Копіювати _URL..." msgid "_Close Tab" msgstr "_Закрити вкладку" msgid "All fields" msgstr "Всі поля" msgid "_Add" msgstr "_Додати" msgid "_Remove" msgstr "_Видалити" msgid "_Clear" msgstr "_Очистити" msgid "Fields selected" msgstr "Вибрані поля" msgid "CSV Parameters" msgstr "Параметри CSV" msgid "Delimiter:" msgstr "Роздільник:" msgid "Quote char:" msgstr "Символ цитати:" msgid "Encoding:" msgstr "Кодування:" msgid "Use locale format" msgstr "Використовувати локальний формат" msgid "Field name" msgstr "Ім'я поля" #, python-format msgid "CSV Export: %s" msgstr "Експорт CSV: %s" msgid "_Save Export" msgstr "_Зберегти експорт" msgid "_URL Export" msgstr "_Експорт URL" msgid "_Delete Export" msgstr "_Видалити експорт" msgid "Predefined exports" msgstr "Попередньо визначені експорти" msgid "Name" msgstr "Ім'я" msgid "Open" msgstr "Відкрити" msgid "Save" msgstr "Зберегти" msgid "Listed Records" msgstr "Записи списку" msgid "Selected Records" msgstr "Вибрані записи" msgid "Ignore search limit" msgstr "Ігнорувати обмеження пошуку" msgid "Add field names" msgstr "Додати імена полів" #, python-format msgid "%s (string)" msgstr "%s (рядок)" #, python-format msgid "%s (model name)" msgstr "%s (ім'я моделі)" #, fuzzy, python-format msgid "%s/Record Name" msgstr "%s (ім'я запису)" msgid "What is the name of this export?" msgstr "Як називається цей експорт?" #, python-format msgid "Override '%s' definition?" msgstr "Перевизначити визначення '%s'?" #, python-format msgid "%d record saved." msgstr "%d запис збережено." #, python-format msgid "%d records saved." msgstr "%d записів збережено." msgid "Export failed" msgstr "Помилка експорту" msgid "Link" msgstr "Посилання" msgid "Delete" msgstr "Видалити" msgid "Discard changes" msgstr "" msgid "Save and New" msgstr "" #, fuzzy msgid "Add and New" msgstr "Додати значення" msgid "Add" msgstr "Додати" msgid "Apply changes" msgstr "" msgid "Switch" msgstr "Переключити" msgid "Remove " msgstr "Видалити " msgid "Create a new record " msgstr "Створити новий запис " msgid "Delete selected record " msgstr "Видалити вибраний запис " msgid "Undelete selected record " msgstr "Відновити вибраний запис " #, python-format msgid "CSV Import: %s" msgstr "Імпорт CSV: %s" msgid "_Auto-Detect" msgstr "_Автовизначення" msgid "File to Import:" msgstr "Файл для імпорту:" msgid "Open..." msgstr "Відкрити..." msgid "Lines to Skip:" msgstr "Рядки для пропуску:" msgid "You must select an import file first." msgstr "Спочатку потрібно вибрати файл імпорту." msgid "Detection failed" msgstr "Помилка визначення" #, python-format msgid "Unknown column header \"%s\"" msgstr "" msgid "Error" msgstr "Помилка" msgid "Import failed" msgstr "Помилка імпорту" #, python-format msgid "%d record imported." msgstr "%d запис імпортовано." #, python-format msgid "%d records imported." msgstr "%d записів імпортовано." msgid "Search" msgstr "Пошук" msgid "New" msgstr "Новий" #, python-format msgid "Search %s" msgstr "Пошук %s" msgid "Wizard" msgstr "Майстер" msgid "ID" msgstr "ID" msgid "Created by" msgstr "Ким створено" msgid "Created at" msgstr "Коли створенно" msgid "Edited by" msgstr "Ким змінено" msgid "Edited at" msgstr "Коли змінено" msgid "Unable to set view tree state" msgstr "Неможливо встановити стан дерева" #, python-format msgid "\"%s\" is not valid according to its domain." msgstr "Значення \"%s\" недійсне відповідно до свого домену." #, python-format msgid "\"%s\" is required." msgstr "Значення \"%s\" обов'язкове." #, python-format msgid "The values of \"%s\" are not valid." msgstr "Значення \"%s\" недійсні." msgid "Pre-validation" msgstr "Попередня перевірка" msgid ":" msgstr ":" msgid "Scan" msgstr "" msgid "Image Size" msgstr "Розмір зображення" msgid "Width:" msgstr "Ширина:" msgid "Height:" msgstr "Висота:" msgid "PNG image (*.png)" msgstr "Зображення PNG (*.png)" msgid "Save As" msgstr "Зберегти як" msgid "Image size too large." msgstr "Розмір зображення завеликий." msgid "Copy" msgstr "Копіювати" msgid "Paste" msgstr "Вставити" msgid ".." msgstr ".." msgid "Open filters" msgstr "Відкрити фільтри" msgid "Show bookmarks of filters" msgstr "Показати збережені фільтри" msgid "Remove this bookmark" msgstr "Видалити цю закладку" msgid "Bookmark this filter" msgstr "Зберегти цей фільтр" msgid "Show active records" msgstr "Показати активні записи" msgid "Show inactive records" msgstr "Показати неактивні записи" msgid "Bookmark Name:" msgstr "Ім'я закладки:" msgid "Find" msgstr "Знайти" msgid "Today" msgstr "Сьогодні" msgid "go back" msgstr "назад" msgid "go forward" msgstr "вперед" msgid "previous year" msgstr "попередній рік" msgid "next year" msgstr "наступний рік" msgid "Day" msgstr "" msgid "Week" msgstr "Тиждень" #, fuzzy msgid "Month" msgstr "Вид місяця" msgid "Select..." msgstr "Вибрати..." msgid "Clear" msgstr "Очистити" msgid "All files" msgstr "Всі файли" msgid "Show plain text" msgstr "Показати звичайний текст" msgid "Add value" msgstr "Додати значення" #, python-format msgid "Remove \"%s\"" msgstr "Видалити \"%s\"" msgid "Images" msgstr "Зображення" msgid "Add existing record" msgstr "Додати існуючий запис" msgid "Remove selected record" msgstr "Видалити вибраний запис" msgid "Open the record " msgstr "Відкрити запис " msgid "Clear the field " msgstr "Очистити поле " msgid "Search a record " msgstr "Пошук запису " msgid "Edit selected record" msgstr "Редагувати вибраний запис" msgid "Delete selected record" msgstr "Видалити вибраний запис" #, python-format msgid "%s%%" msgstr "%s%%" msgid "Choose a language" msgstr "Вибрати мову" msgid "Translation" msgstr "Переклад" msgid "Edit" msgstr "Редагувати" msgid "Fuzzy" msgstr "Нечіткий" msgid "You need to save the record before adding translations." msgstr "Треба зберегти запис перед додаванням перекладів." msgid "No other language available." msgstr "Немає іншої мови." msgid "#ERROR" msgstr "" msgid "Translate view" msgstr "Переклад вкладки" ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1745680285.1274436 tryton-7.0.24/tryton/data/locale/zh_CN/0000755000175000017500000000000015003173635015766 5ustar00cedced././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1745680285.167444 tryton-7.0.24/tryton/data/locale/zh_CN/LC_MESSAGES/0000755000175000017500000000000015003173635017553 5ustar00cedced././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1745680279.0 tryton-7.0.24/tryton/data/locale/zh_CN/LC_MESSAGES/tryton.mo0000644000175000017500000004336415003173627021462 0ustar00cedcedL |*}  %16EHMPTVh  ?K_o      #%4Z jv&  2@ D R`rx     0=P c    &2 D NZ^fm  !2 ; EOTj        & 1 A G L ] b i o v ~         #  !3!:!B!X!j! |!!!!! !!!! !! !!","3" 9" E"O" e" o"z"" """""" " """"#!# 2# =# H# U#`#i#l# q#~# # #######$$.$4$F$b$ q$ |$$$$$$ $$ $$$$%% % !%/%D%U% \%f% w% % % %%%%% % %&& &#&(& 0&;&M& ]&j&{&&& &&&& && ' ''-' ?' J'X' k'u'' ''' ''' ((/(A(P(Y( `( l(x(( ((I((!)5$)8Z))) ) ))#))) * * *2*Q*i** **** ** + + + &+ 3+A+ F+g+n+(u+ ++++%+7+., >,J, O,\,d, k, v,, , , ,,,,, , , , , -- -$- *-7- ?- L-X-Z-k-- -- --$- - ---3.<I.. ...... f00000001 11-121B1E1J1M1Q1S1g1{1111 1 11 112 2<2L2b2y2 2 22 2 2 22 22 2! 3.3 M3Y3 `3 l3 v333 3333 3 3 33#3 4&4 .4 <4I4\4c4v4 }444 4 44 444445*5:5A5X5n5 }55 55 5 5 5 55 5 5 6 6#6*6=6 V6 a6n6 6 66 66 666677!7 87 B7 O7Y7`7s7{7 7 7 7 77 77 7 777 88#8*81888@8G8^8f8m8u88 888 8888 99!959 F9S9Z9 b9m9 t999999 9999 : :: %:2:9: @: M: W:d: k:x::::::::::;.;H; W; b;l;s;z;;; ; ;; ;; ;< <#<<<C<J< i<s<z< <<<<<<< < <<<= = ==0=M=d= k= u= = = = === = = = > > > ">0> 7> A>N>^>q>>>>> >>>> >?!? (? 5?B?U?\?l? ?? ???$? ??@@/@?@U@h@p@ w@ @@@ @@2@ @AA4All fieldsFields selectedPredefined exportsCreate...Search...A new version is available!A_ttachments...About...ActionActions...AddAdd a note to the recordAdd an attachment to the recordAdd and NewAdd existing recordAdd field namesAdd new profileAdd valueAdd...Align CenterAlign LeftAlign RightAll filesAlways ignore this warning.Application ErrorApplication ShortcutsApply changesAre you sure to remove this record?Are you sure to remove those records?Attachment (%s)AttachmentsAttachments (%s)Attachments...Bcc:BodyBoldBookmark Name:Bookmark this filterBy: CSV Export: %sCSV Import: %sCSV ParametersC_onnectCancelCancel connection to the Tryton serverCancel savingCc:Check URL: %sCheck VersionChoose a languageClearClear the field CloseClose TabCodeCode ScannerCollapse all rowsCollapse rowCompareCompare: %sConcurrency ExceptionConnect the Tryton serverCopyCopy URL into clipboardCopy _URL...Copy selected rowsCopy selected textCould not connect to the server.Could not get a session.Create "%s"...Create a new recordCreate a new record Create new relationCreate/Select new lineCreated atCreated at:Created byCreated by:Cut selected textDatabase:Date FormatDayDefaultDeleteDelete selected recordDelete selected record Delimiter:Detection failedDigitsDiscard changesDisplay formatDisplayed date formatDisplayed valueDo you want to proceed?Documentation...DownloadE-Mail...E-mail %sEditEdit User PreferencesEdit selected recordEdit...Edited atEdited byEditionEdition ShortcutsEncoding:ErrorExpand all rowsExpand rowExpand/CollapseExport failedFalseFavoritesFetching databases listField nameFile to Import:FilesFindForeground ColorFormFormatFuzzyGlobalHeight:HelpHost / Database informationHost:IDID:IconsIgnore search limitImage SizeImage size too large.ImagesImport failedIncompatible version of the server.Insert copied rowsItalicJustifyKeyboard Shortcuts...Last Modified at:Last Modified by:Launch actionLimitLimit:Lines to Skip:LinkList EntriesList/Tree ShortcutsListed RecordsLoginLogs (%s)MManage...Mark line for deletion/removalMark line for removalModel:MonthMove CursorMove downMove down of one pageMove leftMove rightMove to bottomMove to parentMove to topMove upMove up of one pageNameNewNextNext RecordNext entryNext tabNoNo action defined.No other language available.No result found.Not Found.Not found.Note (%d/%d)Notes (%s)Notes...OKOpenOpen filtersOpen related recordsOpen relationOpen reportOpen the calendarOpen the record Open...Open/Search relationOptionsOverride '%s' definition?PDA ModePNG image (*.png)PastePaste copied textPlay Sound for Code ScannerPre-validationPreferencePreferencesPreferences...PreviewPreviousPrevious RecordPrevious entryPrevious tabPrintPrint reportPrint...ProfileProfile EditorProfile:QuitQuote char:Record saved.Records not removed.Records removed.RelateRelate...Relation EntriesReload/UndoRemove "%s"Remove Remove FileRemove selected profileRemove selected recordRemove this bookmarkReportReport BugReport...ReportsRevisionRevision:SaveSave AsSave As...Save Column WidthSave Tree StateSave and NewSave this recordSave your current versionScanSearchSearch %sSearch Limit SettingsSearch Limit...Search a record Search menuSee the modified versionSelectSelect FileSelect a colorSelect a revisionSelect allSelect parentSelect your actionSelect...Select/Activate current rowSelected RecordsSelectionSendSend an e-mail using the recordShortcutsShow active recordsShow bookmarks of filtersShow inactive recordsShow plain textShow revisions...Spell CheckingSubject:SwitchSwitch ViewSwitch viewTemplateTextText EntriesText and IconsThe following action requires to close all tabs. Do you want to continue?The number of decimalThe values of "%s" are not valid.This record has been modified do you want to save it?This record has been modified while you were editing it.To:TodayToggle menuToggle rowToggle selectionToo many requests. Try again later.ToolbarTranslate viewTranslationTrueUnable to check for new version.Unable to complete email entryUnable to set locale %sUnable to set view tree stateUndelete selected record UnderlineUnknownUnknown column header "%s"Unmark line for deletionUnselect allUse locale formatUser name:Username:ValueView Logs...View _Logs...WeekWhat is the name of this export?Width:WizardWorking now on the duplicated record(s).Write AnywayYYesYou have to select one record.You must select an import file first.You need to save the record before adding translations.Your selection:_Actions..._Add_Auto-Detect_Cancel_Clear_Close Tab_Copy URL_Delete Export_Delete..._Duplicate_E-Mail..._Export Data..._Import Data..._New_Next_Notes..._Previous_Print..._Relate..._Reload/Undo_Remove_Report..._Save_Save Export_Search_Switch View_URL Exportddevelopment modedisable thread usagego backgo forwardhlogging everything at INFO levelmmodularity, scalability and securitynext yearprevious yearsspecify alternate config filespecify the file used to output logging informationspecify the log level: DEBUG, INFO, WARNING, ERROR, CRITICALspecify the login userspecify the server hostname:portttranslator-creditswyProject-Id-Version: PROJECT VERSION Report-Msgid-Bugs-To: EMAIL@ADDRESS POT-Creation-Date: 2025-04-26 17:11+0200 PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE Last-Translator: FULL NAME Language: zh_CN Language-Team: zh_CN Plural-Forms: nplurals=1; plural=0; MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: 8bit Generated-By: Babel 2.17.0 根据域设置,"%s"不可用."%s" 是必填项.#错误%d 条记录导入成功.%d 记录保存成功.%d 条记录导入成功.%d 记录保存成功.%s (%s)%s (模型名称)%s (字符串)%s%%%s/记录名称, ,........:所有字段选中字段预定义导出设置创建...查找...有新版本可用!附件(_T)...关于(_A)...操作操作...添加为当前记录添加注释为当前记录添加附件添加和新建添加已存在记录添加字段名称(_F)添加新配置添加值添加...居中左对齐右对齐所有文件下次不再提示.程序错误应用程序快捷方式应用更改确定要删除这条记录吗?确定要删除这些记录?附件 (%s)附件附件 (%s)附件...私下抄送:正文粗体书签名称:收藏当前筛选器By: CSV导出:%sCSV导入: %sCSV 参数连机(_O)取消取消与 Tryton 服务器的连接取消保存抄送:检查URL: %s检查版本选择一种语言清除清除字段 关闭关闭标签代码代码扫描仪收起所有收起行比较比较: %s并行操作异常连接 Tryton 服务器复制将URL复制到粘贴板复制URL(_U)...复制所选行复制无法连接服务器.无法获取会话。创建 "%s"...新建记录新建记录 新建关联创建/选择新行创建日期创建日期:创建者创建者:剪切数据库:日期格式日视图默认设置删除删除所选记录删除已选记录 分隔符:检测失败有效数字位数放弃更改显示格式日期显示格式显示值继续此项操作 ?文档...下载用电子邮件发送...电子邮件 %s编辑编辑用户偏好编辑所选记录编辑...编辑时间编辑人编辑编辑快捷方式编码:错误展开所有展开行展开/收起导出失败假收藏(_V)正在获取数据库列表字段名导入文件:文件查找字体颜色表单视图(_F)格式模糊全局高度:帮助主机/数据库信息主机:标识标识:显示图标(_I)忽略搜索限制图片尺寸图片尺寸过大.图片导入失败服务器版本不兼容。插入复制的行斜体两头对齐快捷键(K)...上次修改时间:上次修改人:执行操作限制限制:跳过行:链接列表条目列表/树快捷方式已列出记录登录日志(%s)月管理...标记要删除/删除的行标记要删除的行模型:月视图光标移动下移下移一页左移右移移到底部上一级移到顶端上移上移一页名称新建向后后一条记录下一个条目下一标签(_N)否没有定义任何操作 .没有其他可翻译语言.无符合条件的数据.无符合条件的数据.无符合条件的数据.批注 (%d/%d)注释(%s)注释...确定打开打开筛选器打开关联记录打开关联项打开报告打开日历打开记录 打开...打开或查找关系选项(_O)替换设置 '%s' ?PDA 模式PNG 图片格式 (*.png)粘贴粘贴为代码扫描仪播放声音预校验偏好偏好首选项...预览向前前一条记录上一个条目上一标签(_P)打印打印报告打印...配置配置编辑器配置:退出引用符:记录保存成功.所选记录未删除成功.记录已成功删除.关联关联...关系条目刷新/撤销删除"%s"删除 删除文件删除选择的配置删除所选记录移除书签报告(_R)报告Bug报告...报告(_R)修订版本修订版本:保存另存为另存为...保存列宽度保存导航状态保存和新建保存当前记录保存当前版本扫码查找查找 %s设置查询限制设置查询限制 ...查找记录 搜索菜单查看修改后的版本选择选择文件选择颜色选择修订版本全选选择上一级选择一项操作选择...选择/激活当前行已选记录选择发送使用当前记录发送电子邮件快捷方式显示已启用记录显示筛选器书签显示未启用记录显示纯文本显示修订版本...开启拼写检查主题:切换切换视图切换视图模板显示文字(_T)文字输入文字和图标该操作需要关闭所有标签. 是否继续?小数位数"%s" 的取值无效.记录有改动 是否保存?在你编辑的时候,这条记录已经被更改.收件人:今天切换菜单切换行切换选择状态请求太多,请稍后重试.工具栏翻译视图翻译真无法检查新版本.无法完成电子邮件输入无法设置 locale %s无法设置视图树状态已选记录取消删除 下划线未知未知表头 "%s"取消删除标记取消全选使用 locale 格式用户名:用户名:值浏览日志...日志(_V)...周导出设置的名称是?宽度:向导正在处理重复记录.覆写年是需要选择一条记录.必须先选择要导入的文件.请先保存已翻译条目,然后再增加新翻译 .已选择:操作(_A)...添加(_A)自动设置(_A)取消(_C)清除(_C)关闭标签页(_C)复制(_C)URL删除导出设置(_D)删除(_D)...复制(_D)电子邮件发送(_E)...导出数据(_X)...导入数据(_I)...新建(_N)向后(_N)注释(_N)...向前(_P)打印(_P)...关联(_L)...刷新/撤销(_R)删除(_R)报告(_R)...保存(_S)保存导出设置(_S)查找(_S)切换视图(_S)URL 导出(_S)日开发模式禁用线程回退前进时INFO级别记录日志分模块化,可扩展性和安全性下一年度上一年度秒指定备选配置文件指定用于输出日志记录信息的文件设置日志级别: DEBUG, INFO, WARNING, ERROR, CRITICAL设置登录用户设置服务器主机名:端口号t翻译者 - 贡献星期y././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685599.0 tryton-7.0.24/tryton/data/locale/zh_CN/LC_MESSAGES/tryton.po0000644000175000017500000004547514517761237021503 0ustar00cedced# Translations template for tryton. # Copyright (C) 2016 Tryton # This file is distributed under the same license as the tryton project. # FIRST AUTHOR , 2016. msgid "" msgstr "Content-Type: text/plain; charset=utf-8\n" msgid "specify alternate config file" msgstr "指定备选配置文件" msgid "development mode" msgstr "开发模式" msgid "logging everything at INFO level" msgstr "INFO级别记录日志" msgid "specify the log level: DEBUG, INFO, WARNING, ERROR, CRITICAL" msgstr "设置日志级别: DEBUG, INFO, WARNING, ERROR, CRITICAL" msgid "specify the file used to output logging information" msgstr "指定用于输出日志记录信息的文件" msgid "specify the login user" msgstr "设置登录用户" msgid "specify the server hostname:port" msgstr "设置服务器主机名:端口号" msgid "disable thread usage" msgstr "禁用线程" #, python-format msgid "Unable to set locale %s" msgstr "无法设置 locale %s" msgid ", " msgstr ", " msgid ",..." msgstr ",..." #, python-format msgid "%s (%s)" msgstr "%s (%s)" msgid "Select your action" msgstr "选择一项操作" msgid "No action defined." msgstr "没有定义任何操作 ." msgid "By: " msgstr "By: " msgid "Selection" msgstr "选择" msgid "Cancel" msgstr "取消" msgid "OK" msgstr "确定" msgid "Your selection:" msgstr "已选择:" msgid "Save As..." msgstr "另存为..." msgid "Do you want to proceed?" msgstr "继续此项操作 ?" msgid "Always ignore this warning." msgstr "下次不再提示." msgid "No" msgstr "否" msgid "Yes" msgstr "是" msgid "Concurrency Exception" msgstr "并行操作异常" msgid "This record has been modified while you were editing it." msgstr "在你编辑的时候,这条记录已经被更改." msgid "Cancel saving" msgstr "取消保存" msgid "Compare" msgstr "比较" msgid "See the modified version" msgstr "查看修改后的版本" msgid "Write Anyway" msgstr "覆写" msgid "Save your current version" msgstr "保存当前版本" #, python-format msgid "Compare: %s" msgstr "比较: %s" msgid "Close" msgstr "关闭" msgid "Application Error" msgstr "程序错误" msgid "Report Bug" msgstr "报告Bug" #, python-format msgid "Check URL: %s" msgstr "检查URL: %s" msgid "Unable to check for new version." msgstr "无法检查新版本." msgid "A new version is available!" msgstr "有新版本可用!" msgid "Download" msgstr "下载" msgid "Could not get a session." msgstr "无法获取会话。" msgid "Too many requests. Try again later." msgstr "请求太多,请稍后重试." msgid "Not found." msgstr "无符合条件的数据." msgid "Not Found." msgstr "无符合条件的数据." msgid "..." msgstr "..." msgid "Search..." msgstr "查找..." msgid "Create..." msgstr "创建..." #, python-format msgid "Create \"%s\"..." msgstr "创建 \"%s\"..." msgid "Value" msgstr "值" msgid "Displayed value" msgstr "显示值" msgid "Format" msgstr "格式" msgid "Display format" msgstr "显示格式" msgid "Open the calendar" msgstr "打开日历" msgid "Date Format" msgstr "日期格式" msgid "Displayed date format" msgstr "日期显示格式" msgid "y" msgstr "y" msgid "True" msgstr "真" msgid "t" msgstr "t" msgid "False" msgstr "假" msgid "Digits" msgstr "有效数字位数" msgid "The number of decimal" msgstr "小数位数" msgid "Template" msgstr "模板" msgid "Edit..." msgstr "编辑..." msgid "View Logs..." msgstr "浏览日志..." msgid "Attachments..." msgstr "附件..." msgid "Notes..." msgstr "注释..." msgid "Actions..." msgstr "操作..." msgid "Relate..." msgstr "关联..." msgid "Report..." msgstr "报告..." msgid "Print..." msgstr "打印..." msgid "E-Mail..." msgstr "用电子邮件发送..." msgid "Bold" msgstr "粗体" msgid "Italic" msgstr "斜体" msgid "Underline" msgstr "下划线" msgid "Align Left" msgstr "左对齐" msgid "Align Center" msgstr "居中" msgid "Align Right" msgstr "右对齐" msgid "Justify" msgstr "两头对齐" msgid "Foreground Color" msgstr "字体颜色" msgid "Select a color" msgstr "选择颜色" msgid "Y" msgstr "年" msgid "M" msgstr "月" msgid "w" msgstr "星期" msgid "d" msgstr "日" msgid "h" msgstr "时" msgid "m" msgstr "分" msgid "s" msgstr "秒" msgid "Preferences..." msgstr "首选项..." msgid "Toolbar" msgstr "工具栏" msgid "Default" msgstr "默认设置" msgid "Text and Icons" msgstr "文字和图标" msgid "Text" msgstr "显示文字(_T)" msgid "Icons" msgstr "显示图标(_I)" msgid "Form" msgstr "表单视图(_F)" msgid "Save Column Width" msgstr "保存列宽度" msgid "Save Tree State" msgstr "保存导航状态" msgid "Spell Checking" msgstr "开启拼写检查" msgid "Play Sound for Code Scanner" msgstr "为代码扫描仪播放声音" msgid "PDA Mode" msgstr "PDA 模式" msgid "Search Limit..." msgstr "设置查询限制 ..." msgid "Check Version" msgstr "检查版本" msgid "Options" msgstr "选项(_O)" msgid "Documentation..." msgstr "文档..." msgid "Keyboard Shortcuts..." msgstr "快捷键(K)..." msgid "About..." msgstr "关于(_A)..." msgid "Help" msgstr "帮助" msgid "No result found." msgstr "无符合条件的数据." msgid "Favorites" msgstr "收藏(_V)" msgid "Manage..." msgstr "管理..." msgid "Action" msgstr "操作" msgid "" "The following action requires to close all tabs.\n" "Do you want to continue?" msgstr "" "该操作需要关闭所有标签.\n" "是否继续?" msgid "Application Shortcuts" msgstr "应用程序快捷方式" msgid "Global" msgstr "全局" msgid "Preferences" msgstr "偏好" msgid "Search menu" msgstr "搜索菜单" msgid "Toggle menu" msgstr "切换菜单" msgid "Previous tab" msgstr "上一标签(_P)" msgid "Next tab" msgstr "下一标签(_N)" msgid "Shortcuts" msgstr "快捷方式" msgid "Quit" msgstr "退出" msgid "Edition Shortcuts" msgstr "编辑快捷方式" msgid "Text Entries" msgstr "文字输入" msgid "Cut selected text" msgstr "剪切" msgid "Copy selected text" msgstr "复制" msgid "Paste copied text" msgstr "粘贴" msgid "Next entry" msgstr "下一个条目" msgid "Previous entry" msgstr "上一个条目" msgid "Relation Entries" msgstr "关系条目" msgid "Create new relation" msgstr "新建关联" msgid "Open/Search relation" msgstr "打开或查找关系" msgid "List Entries" msgstr "列表条目" msgid "Switch view" msgstr "切换视图" msgid "Create/Select new line" msgstr "创建/选择新行" msgid "Open relation" msgstr "打开关联项" msgid "Mark line for deletion/removal" msgstr "标记要删除/删除的行" msgid "Mark line for removal" msgstr "标记要删除的行" msgid "Unmark line for deletion" msgstr "取消删除标记" msgid "List/Tree Shortcuts" msgstr "列表/树快捷方式" msgid "Move Cursor" msgstr "光标移动" msgid "Move right" msgstr "右移" msgid "Move left" msgstr "左移" msgid "Move up" msgstr "上移" msgid "Move down" msgstr "下移" msgid "Move up of one page" msgstr "上移一页" msgid "Move down of one page" msgstr "下移一页" msgid "Move to top" msgstr "移到顶端" msgid "Move to bottom" msgstr "移到底部" msgid "Move to parent" msgstr "上一级" msgid "Edition" msgstr "编辑" msgid "Copy selected rows" msgstr "复制所选行" msgid "Insert copied rows" msgstr "插入复制的行" msgid "Select all" msgstr "全选" msgid "Unselect all" msgstr "取消全选" msgid "Select parent" msgstr "选择上一级" msgid "Select/Activate current row" msgstr "选择/激活当前行" msgid "Toggle selection" msgstr "切换选择状态" msgid "Expand/Collapse" msgstr "展开/收起" msgid "Expand row" msgstr "展开行" msgid "Collapse row" msgstr "收起行" msgid "Toggle row" msgstr "切换行" msgid "Collapse all rows" msgstr "收起所有" msgid "Expand all rows" msgstr "展开所有" msgid "Close Tab" msgstr "关闭标签" msgid "modularity, scalability and security" msgstr "模块化,可扩展性和安全性" msgid "translator-credits" msgstr "翻译者 - 贡献" #, python-format msgid "Attachments (%s)" msgstr "附件 (%s)" msgid "Code Scanner" msgstr "代码扫描仪" msgid "Code" msgstr "代码" msgid "Profile Editor" msgstr "配置编辑器" msgid "Profile" msgstr "配置" msgid "Add new profile" msgstr "添加新配置" msgid "Remove selected profile" msgstr "删除选择的配置" msgid "Host:" msgstr "主机:" msgid "Database:" msgstr "数据库:" msgid "Fetching databases list" msgstr "正在获取数据库列表" msgid "Username:" msgstr "用户名:" msgid "Incompatible version of the server." msgstr "服务器版本不兼容。" msgid "Could not connect to the server." msgstr "无法连接服务器." msgid "Login" msgstr "登录" msgid "_Cancel" msgstr "取消(_C)" msgid "Cancel connection to the Tryton server" msgstr "取消与 Tryton 服务器的连接" msgid "C_onnect" msgstr "连机(_O)" msgid "Connect the Tryton server" msgstr "连接 Tryton 服务器" msgid "Profile:" msgstr "配置:" msgid "Host / Database information" msgstr "主机/数据库信息" msgid "User name:" msgstr "用户名:" msgid "Unable to complete email entry" msgstr "无法完成电子邮件输入" #, python-format msgid "E-mail %s" msgstr "电子邮件 %s" msgid "To:" msgstr "收件人:" msgid "Cc:" msgstr "抄送:" msgid "Bcc:" msgstr "私下抄送:" msgid "Subject:" msgstr "主题:" msgid "Body" msgstr "正文" msgid "Reports" msgstr "报告(_R)" msgid "Attachments" msgstr "附件" msgid "Files" msgstr "文件" msgid "Send" msgstr "发送" msgid "Select File" msgstr "选择文件" msgid "Remove File" msgstr "删除文件" msgid "Select" msgstr "选择" msgid "Add..." msgstr "添加..." msgid "Preview" msgstr "预览" msgid "Previous" msgstr "向前" msgid "Next" msgstr "向后" #, python-format msgid "Attachment (%s)" msgstr "附件 (%s)" #, python-format msgid "Note (%d/%d)" msgstr "批注 (%d/%d)" msgid "You have to select one record." msgstr "需要选择一条记录." msgid "Are you sure to remove this record?" msgstr "确定要删除这条记录吗?" msgid "Are you sure to remove those records?" msgstr "确定要删除这些记录?" msgid "Records not removed." msgstr "所选记录未删除成功." msgid "Records removed." msgstr "记录已成功删除." msgid "Working now on the duplicated record(s)." msgstr "正在处理重复记录." msgid "Record saved." msgstr "记录保存成功." msgid "" "This record has been modified\n" "do you want to save it?" msgstr "" "记录有改动\n" "是否保存?" msgid "Launch action" msgstr "执行操作" msgid "Relate" msgstr "关联" msgid "Open related records" msgstr "打开关联记录" msgid "Report" msgstr "报告(_R)" msgid "Open report" msgstr "打开报告" msgid "Print" msgstr "打印" msgid "Print report" msgstr "打印报告" msgid "_Copy URL" msgstr "复制(_C)URL" msgid "Copy URL into clipboard" msgstr "将URL复制到粘贴板" msgid "Unknown" msgstr "未知" msgid "Limit" msgstr "限制" msgid "Search Limit Settings" msgstr "设置查询限制" msgid "Limit:" msgstr "限制:" #, python-format msgid "Logs (%s)" msgstr "日志(%s)" msgid "Model:" msgstr "模型:" msgid "ID:" msgstr "标识:" msgid "Created by:" msgstr "创建者:" msgid "Created at:" msgstr "创建日期:" msgid "Last Modified by:" msgstr "上次修改人:" msgid "Last Modified at:" msgstr "上次修改时间:" #, python-format msgid "Notes (%s)" msgstr "注释(%s)" msgid "Edit User Preferences" msgstr "编辑用户偏好" msgid "Preference" msgstr "偏好" msgid "Revision" msgstr "修订版本" msgid "Select a revision" msgstr "选择修订版本" msgid "Revision:" msgstr "修订版本:" msgid "_Switch View" msgstr "切换视图(_S)" msgid "Switch View" msgstr "切换视图" msgid "_Previous" msgstr "向前(_P)" msgid "Previous Record" msgstr "前一条记录" msgid "_Next" msgstr "向后(_N)" msgid "Next Record" msgstr "后一条记录" msgid "_Search" msgstr "查找(_S)" msgid "_New" msgstr "新建(_N)" msgid "Create a new record" msgstr "新建记录" msgid "_Save" msgstr "保存(_S)" msgid "Save this record" msgstr "保存当前记录" msgid "_Reload/Undo" msgstr "刷新/撤销(_R)" msgid "Reload/Undo" msgstr "刷新/撤销" msgid "_Duplicate" msgstr "复制(_D)" msgid "_Delete..." msgstr "删除(_D)..." msgid "View _Logs..." msgstr "日志(_V)..." msgid "Show revisions..." msgstr "显示修订版本..." msgid "A_ttachments..." msgstr "附件(_T)..." msgid "Add an attachment to the record" msgstr "为当前记录添加附件" msgid "_Notes..." msgstr "注释(_N)..." msgid "Add a note to the record" msgstr "为当前记录添加注释" msgid "_Actions..." msgstr "操作(_A)..." msgid "_Relate..." msgstr "关联(_L)..." msgid "_Report..." msgstr "报告(_R)..." msgid "_Print..." msgstr "打印(_P)..." msgid "_E-Mail..." msgstr "电子邮件发送(_E)..." msgid "Send an e-mail using the record" msgstr "使用当前记录发送电子邮件" msgid "_Export Data..." msgstr "导出数据(_X)..." msgid "_Import Data..." msgstr "导入数据(_I)..." msgid "Copy _URL..." msgstr "复制URL(_U)..." msgid "_Close Tab" msgstr "关闭标签页(_C)" msgid "All fields" msgstr "所有字段" msgid "_Add" msgstr "添加(_A)" msgid "_Remove" msgstr "删除(_R)" msgid "_Clear" msgstr "清除(_C)" msgid "Fields selected" msgstr "选中字段" msgid "CSV Parameters" msgstr "CSV 参数" msgid "Delimiter:" msgstr "分隔符:" msgid "Quote char:" msgstr "引用符:" msgid "Encoding:" msgstr "编码:" msgid "Use locale format" msgstr "使用 locale 格式" msgid "Field name" msgstr "字段名" #, python-format msgid "CSV Export: %s" msgstr "CSV导出:%s" msgid "_Save Export" msgstr "保存导出设置(_S)" msgid "_URL Export" msgstr "URL 导出(_S)" msgid "_Delete Export" msgstr "删除导出设置(_D)" msgid "Predefined exports" msgstr "预定义导出设置" msgid "Name" msgstr "名称" msgid "Open" msgstr "打开" msgid "Save" msgstr "保存" msgid "Listed Records" msgstr "已列出记录" msgid "Selected Records" msgstr "已选记录" msgid "Ignore search limit" msgstr "忽略搜索限制" msgid "Add field names" msgstr "添加字段名称(_F)" #, python-format msgid "%s (string)" msgstr "%s (字符串)" #, python-format msgid "%s (model name)" msgstr "%s (模型名称)" #, python-format msgid "%s/Record Name" msgstr "%s/记录名称" msgid "What is the name of this export?" msgstr "导出设置的名称是?" #, python-format msgid "Override '%s' definition?" msgstr "替换设置 '%s' ?" #, python-format msgid "%d record saved." msgstr "%d 记录保存成功." #, python-format msgid "%d records saved." msgstr "%d 记录保存成功." msgid "Export failed" msgstr "导出失败" msgid "Link" msgstr "链接" msgid "Delete" msgstr "删除" msgid "Discard changes" msgstr "放弃更改" msgid "Save and New" msgstr "保存和新建" msgid "Add and New" msgstr "添加和新建" msgid "Add" msgstr "添加" msgid "Apply changes" msgstr "应用更改" msgid "Switch" msgstr "切换" msgid "Remove " msgstr "删除 " msgid "Create a new record " msgstr "新建记录 " msgid "Delete selected record " msgstr "删除已选记录 " msgid "Undelete selected record " msgstr "已选记录取消删除 " #, python-format msgid "CSV Import: %s" msgstr "CSV导入: %s" msgid "_Auto-Detect" msgstr "自动设置(_A)" msgid "File to Import:" msgstr "导入文件:" msgid "Open..." msgstr "打开..." msgid "Lines to Skip:" msgstr "跳过行:" msgid "You must select an import file first." msgstr "必须先选择要导入的文件." msgid "Detection failed" msgstr "检测失败" #, python-format msgid "Unknown column header \"%s\"" msgstr "未知表头 \"%s\"" msgid "Error" msgstr "错误" msgid "Import failed" msgstr "导入失败" #, python-format msgid "%d record imported." msgstr "%d 条记录导入成功." #, python-format msgid "%d records imported." msgstr "%d 条记录导入成功." msgid "Search" msgstr "查找" msgid "New" msgstr "新建" #, python-format msgid "Search %s" msgstr "查找 %s" msgid "Wizard" msgstr "向导" msgid "ID" msgstr "标识" msgid "Created by" msgstr "创建者" msgid "Created at" msgstr "创建日期" msgid "Edited by" msgstr "编辑人" msgid "Edited at" msgstr "编辑时间" msgid "Unable to set view tree state" msgstr "无法设置视图树状态" #, python-format msgid "\"%s\" is not valid according to its domain." msgstr "根据域设置,\"%s\"不可用." #, python-format msgid "\"%s\" is required." msgstr "\"%s\" 是必填项." #, python-format msgid "The values of \"%s\" are not valid." msgstr "\"%s\" 的取值无效." msgid "Pre-validation" msgstr "预校验" msgid ":" msgstr ":" msgid "Scan" msgstr "扫码" msgid "Image Size" msgstr "图片尺寸" msgid "Width:" msgstr "宽度:" msgid "Height:" msgstr "高度:" msgid "PNG image (*.png)" msgstr "PNG 图片格式 (*.png)" msgid "Save As" msgstr "另存为" msgid "Image size too large." msgstr "图片尺寸过大." msgid "Copy" msgstr "复制" msgid "Paste" msgstr "粘贴" msgid ".." msgstr ".." msgid "Open filters" msgstr "打开筛选器" msgid "Show bookmarks of filters" msgstr "显示筛选器书签" msgid "Remove this bookmark" msgstr "移除书签" msgid "Bookmark this filter" msgstr "收藏当前筛选器" msgid "Show active records" msgstr "显示已启用记录" msgid "Show inactive records" msgstr "显示未启用记录" msgid "Bookmark Name:" msgstr "书签名称:" msgid "Find" msgstr "查找" msgid "Today" msgstr "今天" msgid "go back" msgstr "回退" msgid "go forward" msgstr "前进" msgid "previous year" msgstr "上一年度" msgid "next year" msgstr "下一年度" msgid "Day" msgstr "日视图" msgid "Week" msgstr "周" msgid "Month" msgstr "月视图" msgid "Select..." msgstr "选择..." msgid "Clear" msgstr "清除" msgid "All files" msgstr "所有文件" msgid "Show plain text" msgstr "显示纯文本" msgid "Add value" msgstr "添加值" #, python-format msgid "Remove \"%s\"" msgstr "删除\"%s\"" msgid "Images" msgstr "图片" msgid "Add existing record" msgstr "添加已存在记录" msgid "Remove selected record" msgstr "删除所选记录" msgid "Open the record " msgstr "打开记录 " msgid "Clear the field " msgstr "清除字段 " msgid "Search a record " msgstr "查找记录 " msgid "Edit selected record" msgstr "编辑所选记录" msgid "Delete selected record" msgstr "删除所选记录" #, python-format msgid "%s%%" msgstr "%s%%" msgid "Choose a language" msgstr "选择一种语言" msgid "Translation" msgstr "翻译" msgid "Edit" msgstr "编辑" msgid "Fuzzy" msgstr "模糊" msgid "You need to save the record before adding translations." msgstr "请先保存已翻译条目,然后再增加新翻译 ." msgid "No other language available." msgstr "没有其他可翻译语言." msgid "#ERROR" msgstr "#错误" msgid "Translate view" msgstr "翻译视图" ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1745680285.1274436 tryton-7.0.24/tryton/data/pixmaps/0000755000175000017500000000000015003173635015207 5ustar00cedced././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1745680285.1974444 tryton-7.0.24/tryton/data/pixmaps/tryton/0000755000175000017500000000000015003173635016546 5ustar00cedced././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685599.0 tryton-7.0.24/tryton/data/pixmaps/tryton/LICENSE0000644000175000017500000002613514517761237017574 0ustar00cedced Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685599.0 tryton-7.0.24/tryton/data/pixmaps/tryton/tryton-add.svg0000644000175000017500000000027114517761237021366 0ustar00cedced ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685599.0 tryton-7.0.24/tryton/data/pixmaps/tryton/tryton-archive.svg0000644000175000017500000000057614517761237022267 0ustar00cedced ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685599.0 tryton-7.0.24/tryton/data/pixmaps/tryton/tryton-arrow-down.svg0000644000175000017500000000074114517761237022737 0ustar00cedced ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685599.0 tryton-7.0.24/tryton/data/pixmaps/tryton/tryton-arrow-left.svg0000644000175000017500000000074314517761237022724 0ustar00cedced ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685599.0 tryton-7.0.24/tryton/data/pixmaps/tryton/tryton-arrow-right.svg0000644000175000017500000000074114517761237023105 0ustar00cedced ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685599.0 tryton-7.0.24/tryton/data/pixmaps/tryton/tryton-arrow-up.svg0000644000175000017500000000030414517761237022407 0ustar00cedced ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685599.0 tryton-7.0.24/tryton/data/pixmaps/tryton/tryton-attach.svg0000644000175000017500000000062214517761237022102 0ustar00cedced ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685599.0 tryton-7.0.24/tryton/data/pixmaps/tryton/tryton-back.svg0000644000175000017500000000033214517761237021534 0ustar00cedced ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685599.0 tryton-7.0.24/tryton/data/pixmaps/tryton/tryton-barcode-scanner.svg0000644000175000017500000000054614517761237023671 0ustar00cedced././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685599.0 tryton-7.0.24/tryton/data/pixmaps/tryton/tryton-bookmark-border.svg0000644000175000017500000000035414517761237023720 0ustar00cedced ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685599.0 tryton-7.0.24/tryton/data/pixmaps/tryton/tryton-bookmark.svg0000644000175000017500000000032114517761237022437 0ustar00cedced ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685599.0 tryton-7.0.24/tryton/data/pixmaps/tryton/tryton-bookmarks.svg0000644000175000017500000000042314517761237022625 0ustar00cedced ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685599.0 tryton-7.0.24/tryton/data/pixmaps/tryton/tryton-cancel.svg0000644000175000017500000000050514517761237022063 0ustar00cedced ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685599.0 tryton-7.0.24/tryton/data/pixmaps/tryton/tryton-clear.svg0000644000175000017500000000037314517761237021727 0ustar00cedced ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685599.0 tryton-7.0.24/tryton/data/pixmaps/tryton/tryton-close.svg0000644000175000017500000000037314517761237021746 0ustar00cedced ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685599.0 tryton-7.0.24/tryton/data/pixmaps/tryton/tryton-copy.svg0000644000175000017500000000043614517761237021613 0ustar00cedced ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685599.0 tryton-7.0.24/tryton/data/pixmaps/tryton/tryton-create.svg0000644000175000017500000000040714517761237022102 0ustar00cedced ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685599.0 tryton-7.0.24/tryton/data/pixmaps/tryton/tryton-date.svg0000644000175000017500000000040414517761237021551 0ustar00cedced ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685599.0 tryton-7.0.24/tryton/data/pixmaps/tryton/tryton-delete.svg0000644000175000017500000000034314517761237022100 0ustar00cedced ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685599.0 tryton-7.0.24/tryton/data/pixmaps/tryton/tryton-download.svg0000644000175000017500000000040314517761237022442 0ustar00cedced././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685599.0 tryton-7.0.24/tryton/data/pixmaps/tryton/tryton-drag.svg0000644000175000017500000000065714517761237021563 0ustar00cedced././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685599.0 tryton-7.0.24/tryton/data/pixmaps/tryton/tryton-email.svg0000644000175000017500000000040314517761237021722 0ustar00cedced ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685599.0 tryton-7.0.24/tryton/data/pixmaps/tryton/tryton-error.svg0000644000175000017500000000036614517761237021774 0ustar00cedced ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685599.0 tryton-7.0.24/tryton/data/pixmaps/tryton/tryton-exit.svg0000644000175000017500000000047214517761237021612 0ustar00cedced ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685599.0 tryton-7.0.24/tryton/data/pixmaps/tryton/tryton-export.svg0000644000175000017500000000051014517761237022153 0ustar00cedced ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685599.0 tryton-7.0.24/tryton/data/pixmaps/tryton/tryton-filter.svg0000644000175000017500000000030414517761237022120 0ustar00cedced ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685599.0 tryton-7.0.24/tryton/data/pixmaps/tryton/tryton-format-align-center.svg0000644000175000017500000000034114517761237024472 0ustar00cedced ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685599.0 tryton-7.0.24/tryton/data/pixmaps/tryton/tryton-format-align-justify.svg0000644000175000017500000000034014517761237024706 0ustar00cedced ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685599.0 tryton-7.0.24/tryton/data/pixmaps/tryton/tryton-format-align-left.svg0000644000175000017500000000034214517761237024145 0ustar00cedced ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685599.0 tryton-7.0.24/tryton/data/pixmaps/tryton/tryton-format-align-right.svg0000644000175000017500000000034114517761237024327 0ustar00cedced ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685599.0 tryton-7.0.24/tryton/data/pixmaps/tryton/tryton-format-bold.svg0000644000175000017500000000057314517761237023051 0ustar00cedced ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685599.0 tryton-7.0.24/tryton/data/pixmaps/tryton/tryton-format-color-text.svg0000644000175000017500000000043014517761237024221 0ustar00cedced ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685599.0 tryton-7.0.24/tryton/data/pixmaps/tryton/tryton-format-italic.svg0000644000175000017500000000030614517761237023370 0ustar00cedced ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685599.0 tryton-7.0.24/tryton/data/pixmaps/tryton/tryton-format-underline.svg0000644000175000017500000000041114517761237024105 0ustar00cedced ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685599.0 tryton-7.0.24/tryton/data/pixmaps/tryton/tryton-forward.svg0000644000175000017500000000033614517761237022304 0ustar00cedced ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685599.0 tryton-7.0.24/tryton/data/pixmaps/tryton/tryton-history.svg0000644000175000017500000000056614517761237022346 0ustar00cedced ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685599.0 tryton-7.0.24/tryton/data/pixmaps/tryton/tryton-icon.png0000644000175000017500000000142214517761237021552 0ustar00cedcedPNG  IHDR DsRGB8PLTE !"## %!&"' $)"'+%',&).'*/(,0*-1+.3-05.16/4827<68<68=7:>8BF@GKFKOIMPKNQLOSNPTOSVQVZUY]X\_Z]a\_c^`c_beacfaehceidmpknqmpsoqtptvruxtz}y|~{}tRNS@fbKGDH pHYs@ tIME 1NFIDAT8˅V@T wFAE% -lMcgw=" QSB. HyZvYG=jXmEP 8Y` .lcAЫ=l%././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685599.0 tryton-7.0.24/tryton/data/pixmaps/tryton/tryton-import.svg0000644000175000017500000000051214517761237022146 0ustar00cedced ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685599.0 tryton-7.0.24/tryton/data/pixmaps/tryton/tryton-info.svg0000644000175000017500000000036614517761237021576 0ustar00cedced ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685599.0 tryton-7.0.24/tryton/data/pixmaps/tryton/tryton-launch.svg0000644000175000017500000000046314517761237022113 0ustar00cedced ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685599.0 tryton-7.0.24/tryton/data/pixmaps/tryton/tryton-link.svg0000644000175000017500000000055114517761237021574 0ustar00cedced ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685599.0 tryton-7.0.24/tryton/data/pixmaps/tryton/tryton-log.svg0000644000175000017500000000053614517761237021423 0ustar00cedced ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685599.0 tryton-7.0.24/tryton/data/pixmaps/tryton/tryton-menu.svg0000644000175000017500000000030314517761237021576 0ustar00cedced ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685599.0 tryton-7.0.24/tryton/data/pixmaps/tryton/tryton-note.svg0000644000175000017500000000046314517761237021606 0ustar00cedced ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685599.0 tryton-7.0.24/tryton/data/pixmaps/tryton/tryton-ok.svg0000644000175000017500000000030714517761237021247 0ustar00cedced ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685599.0 tryton-7.0.24/tryton/data/pixmaps/tryton/tryton-open.svg0000644000175000017500000000043014517761237021574 0ustar00cedced ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685599.0 tryton-7.0.24/tryton/data/pixmaps/tryton/tryton-print.svg0000644000175000017500000000044714517761237021777 0ustar00cedced ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685599.0 tryton-7.0.24/tryton/data/pixmaps/tryton/tryton-public.svg0000644000175000017500000000070214517761237022113 0ustar00cedced ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685599.0 tryton-7.0.24/tryton/data/pixmaps/tryton/tryton-question.svg0000644000175000017500000000062614517761237022511 0ustar00cedced././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685599.0 tryton-7.0.24/tryton/data/pixmaps/tryton/tryton-refresh.svg0000644000175000017500000000054014517761237022273 0ustar00cedced ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685599.0 tryton-7.0.24/tryton/data/pixmaps/tryton/tryton-remove.svg0000644000175000017500000000024714517761237022136 0ustar00cedced ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685599.0 tryton-7.0.24/tryton/data/pixmaps/tryton/tryton-save.svg0000644000175000017500000000056114517761237021576 0ustar00cedced././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685599.0 tryton-7.0.24/tryton/data/pixmaps/tryton/tryton-search.svg0000644000175000017500000000060014517761237022077 0ustar00cedced ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685599.0 tryton-7.0.24/tryton/data/pixmaps/tryton/tryton-send.svg0000644000175000017500000000025714517761237021573 0ustar00cedced././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685599.0 tryton-7.0.24/tryton/data/pixmaps/tryton/tryton-sound-off.svg0000644000175000017500000000045014517761237022535 0ustar00cedced././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685599.0 tryton-7.0.24/tryton/data/pixmaps/tryton/tryton-sound-on.svg0000644000175000017500000000044414517761237022402 0ustar00cedced././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685599.0 tryton-7.0.24/tryton/data/pixmaps/tryton/tryton-star-border.svg0000644000175000017500000000052614517761237023065 0ustar00cedced ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685599.0 tryton-7.0.24/tryton/data/pixmaps/tryton/tryton-star.svg0000644000175000017500000000043014517761237021604 0ustar00cedced ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685599.0 tryton-7.0.24/tryton/data/pixmaps/tryton/tryton-switch.svg0000644000175000017500000000033514517761237022140 0ustar00cedced ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685599.0 tryton-7.0.24/tryton/data/pixmaps/tryton/tryton-translate.svg0000644000175000017500000000067614517761237022644 0ustar00cedced ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685599.0 tryton-7.0.24/tryton/data/pixmaps/tryton/tryton-unarchive.svg0000644000175000017500000000105614517761237022624 0ustar00cedced ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685599.0 tryton-7.0.24/tryton/data/pixmaps/tryton/tryton-undo.svg0000644000175000017500000000044414517761237021605 0ustar00cedced ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685599.0 tryton-7.0.24/tryton/data/pixmaps/tryton/tryton-warning.svg0000644000175000017500000000031014517761237022275 0ustar00cedced ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685599.0 tryton-7.0.24/tryton/data/pixmaps/tryton/tryton.icns0000644000175000017500000006411214517761237021001 0ustar00cedcedicnshJICN#????il32@ 7O  2zZ-+ "8 !1 K;`v{5b1Wx%c >dFD L$kf g6 !Q@$ tu -=4ﭔ8.T Y1*tc ލo +$ ; яdk 1! 'OQ9k(Rށ 4MTG/"!    "!! !!$!!  !$ !  ;S% " 6}] 1/ !' = ! &6 $ ! O? cy  ~9 f6 $Z z* g $ #B   h  JH $P ) ni j$ : $ &U D) wx 2A 8ﯖ = 3X   \5 #. wf !ޏ! r 0)@ ёgo 5& !+ST !  =n ! -Uބ  " "9QWK3 " !!  " " !! !! " 5M 0yX+)  6 0I9^uz3a/Uv$b<cEC J"ie e4 O?# st +<2ﭓ6,S X/(sa ތn *": Ўbj} / %MO7j&P݀ 3KRE-l8mkMߦN " ki}s*' I[it32 1LN)Cp CU&І8Nj;U|"G,')"SHR_$ދJ}ЬʌQ$ڊvsЉ2݊UWasB(57}5fpJl_p&ׅrZlΟIɅ08Xc'ŅI0مJ 0fDLYKmLrSbNQ\φ@O 0f>'#CQ^J"P_ '݁+qQ0ݜ"]R_֜Lʦ(0Ϝ}KYQǜŅQq'a"HF̅LhFԇMR҅+2 ʌ0q"gv!؆^Ј̗Rr%5!EؖHU*?߉*mfmGۉ 'a1 C)^&=y/Yy9ܟ&G,9ȋgxl_M)%1RʠnݍhUɎ"uԦ͏BZ{SL>FArY#@#m)9Zdc3ˇ43JrC{`ZTqMiKUk.N ز*Ŏu(jhg>,Г~~<Aa"ES$cL-0lnNǽ1m?Qʵ4p S̬.5rL"Vѣi;xf%\֚b@~<(b֐n.WԅҿA!0>NXI7$              6OR.  Gs%  GX  "* !ц!  <ȍ? Y  ' K  1 +  .  & WL  Vb )ߋ  N$ Ѭ  ʌT )ۊ!  x v$  # $щ  7݊ Y  [ dv  F  $ˆ -9 ; : is N o bs *ׅ u] oΟ M "ʅ4 < [g , ƅ M5 څ " N% 4 i H O \ O p P u W e R™ T ` ІD S" %4 i B, (GTaN' S# b% ,݁0 t $# U4 †ޜ &` Vb ֜ P˦ - 4Ϝ O \ TȜ ƅU t #+ d' LJ "ͅ O lJ Շ QV #҅/ 7 %ˌ4 t& j y &؆" ˜ a" ! ш͗ Vu* 9& Iٖ LY . C߉. pi! p" Kۉ% +d6 %G. a* A |3 \| "> #ܟ *K 1 >ɋj {o c Q" #. * 6V ˠ$ "qݍ k# Yʎ& xըΏF ]~ W" PB J Eu\( D( p. >] hf 7 !̇ 87 Nu G} d^ W# tQ lO Yn 2R %ز. Ǝx- m ljC" 1 $ѓš@ E d& IW  )f  "O2  4oq  #RȽ!  6pC  $Tʵ  8s"  %Wͬ2  :uP  &Zңl  ?{i  *`֚e  D@  -e֐q!  2[ՅE  &4CR[M;)              /JM'AoBS$І6NJ9T{ F*%' RGQ]"ދH|ЬɌO"ڊtrЉ0܊TV`r@&35|4eoHj]o$ׅqXj͟Hȅ.6Wb%ąH/مH.eCJWIlKqR`MO[φ>M.e<%!BO\H N]%܁*p~P.ݜ \Q]֜Kʦ&.Μ|IWOǜŅPp%` GD̅JgDԇLQ҅)0ʌ.o eu׆\Ј̗Qq#3CؖGT(>߉(kekEۉ%`/B'\$;x-Wx8ܟ$E*8ȋewj^L'#/Qʠm܍fTȎ tԦ͏@XzRK<D?qW!>!l'8Xca1ˇ21HqBy_YRoLhITj,Mײ(Ŏt&ige=*Г}}:?` CR"aJ+.jmMǽ/l>Oɵ2oR̬,4qK Uѣh9we#[֚`>}:&`֐m,VԅҾ?.=MWH5" t8mk@(^̽g/RREIqt} $[`mlJE+&818-TMUQ¹|kG+ |j8*x` ghPa} t'`s%lE6 J|굀IDv˾V ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685599.0 tryton-7.0.24/tryton/data/pixmaps/tryton/tryton.ico0000644000175000017500000017233614517761237020627 0ustar00cedced  -00 %.  8T(d00  F&00 6PNG  IHDR\rfsRGBgAMA a-IDATx XM1U\D2Oi5dL  2e.RDd!u%4 ) q}y=λY.(_|wVNFjFvFvVf,g^degeD zmyye>}T7!OT*@(ʔX|wtJJUUQɁ% ʕ-VړꪪTU՟>PxRj LEHn8 C%Rz{SSS޾sMӟץ.MpJ]7tu_oNC-[VrlHIIx S'Oo킂uABVWhobblrج !(ٕO>ĩOt|.u+B^V+䰉iE3xEI>v2ϼOyeF%ooһgϝ:)u^̙fwtܷ+R!G J~֥{ND"/_ݵ+rPՓoiI]Pviջ7jԘÇ(w )e׍Y=#+6=vjիe9v^4v*tBGtXt7mSSMd)jYO d*.3^~ECzQ<5k~E__\ x_{ކu/A $|AEOO ^^7vm?=>`-W- ;-H~0yh gϜ3cqd ƍ/n-=&-zzX>.Orus%#z4 ڶ9%<Ǐk fw4^֭͑ι<|!IէGAQ555q&iiiuLNK{M Sw2]vO z Am۱g %%w.r];wYnU-b ϢkCR BLj[p` 66zR/p*`nn(Coo>|I$z?|E%>J$ ZA$dƍ9 [rR^}hݪ5QT\M^$.^Q䀪U+H9|A_K<N aWNH#14S&M6yw 77fV>|P§C|k׾X+(( (tܺ})@kǴEo$  \8BԩkBւfmzq/X /s^RP$'Q+SпE/_IaS|ʕ+[v@ Xi/;; <}< |x9m{wۺLt)f`!ӂkC!du0wBX>tNBJ*A:" ϟgZ'J.-cx4g @/a6m Z ˿UV`3ڵՃ?WCy&$J#Ikzyv#k$>S ??3ʔxouLM77ש -?~zM(<}tF ud~0s;M@\XrSR0y<C' Wi\;sı..LΝ _OSeZI>:Ԃ=3Spthή'O)|~[)?qv Aжu[VŅ'~'z!`m`5/x|?:c۝*†A~uW- @C FGF;]5kw`ѱͻ7&p| 4C tM=oG=˵GuC-Zo?(Pnqag柳Co^ ug.d7DEo}.noj5eam">M@"?z7 o ^\k%y' µ7y#fx ίVUgPOeѤPL<ҺU".WSSom\6iJ V,[f2kONsx{Mow|L-Zux<*A`t yy= 'V{V-dgfi0=8,d ggmX8ϮxCѦmLf-G"fs^ͭ"BWn q{ `M6j޾^( *URՠΧ/_K [ݻvuR',RS2^ /ʗ+FYOCpLmsk+ P 9)а&cwnƬiOD6sJ|2i0l=BPUUGXkw2RpIN wLڎ;!~&?Z{ǒad , @p 짋)ȯA&tmb`[V ,  )b7U5&H$M6>,qajb($3 :5ƆJ[XO^T""[vvCVv6 M_Ç`4W * 4k6oa%U ;S@! S'Oc-<~ Vp6 a-+~p=?:a2p`Pc;*UTQmI@*3>^~ЯO?V"5ӫ/ `<?r*07`~05 nZ$𵛘Aix8 zNGaQ['`ƺ8YJLt`iy5XQ@Id|УtAγUO * lQ\~ L=_<typ@A% C.$I:۝`&Aph @n6J!,҂UA soeK 6`񏄃-#=-;Ł=;w|NcVڢQ gQvUJ[Z;#'rʬ5x ~e$`3fϜ#糳[Jv짏e=Ld J. {@MMM6yC]J0jڳLT$^ )܇VJ%hbBV",5~ 6Q*IS&G3 o ^Gho܎0q6&O<s0bu, S:v7{p1+>6U&qC+J}A}Xi M@ QNaD?,(Wΰ>lkJ[W^C #JxJcC)(} \`ȱԉD"8"X K$I7 P5Up{;;vE_/a$ڸ-)'Nq*|GhѼ%lݸf ~*@R ~C`Ŭ|RXq=a$8&H^0e Ũ `(_<3 ^m(z{9s7}T8H^$KcqIE@Qa3AjԨ)q'@dOb!^j- 0v0̜>KIv!??_RL8{Uʺ//@P}LHמ鳧 [k;p-籀H~=;VƊl56 '19-?57%233Q#ܾsKMLu^R. g{ {1՝6aVX" z:uW^A̾=qK8deenwᶗٯ;*DC[639[Eַ¢% !zO `۽:>|,_L! Xk͛`lNexf҂밌 `H Z^K!Nl1E@e{LE6^ȝ$lwzԓq1]PdIf cTZF2 Nl[7IG0oğ/((a#yC1MMs(̙ d{`F H_"A[mcf#&dm^xG9(ˏɇArdvEK歛H˟]a%R1kL'׊MO[u c;Q999$R@$b b4ґ LwGtQge6v^KsK\R67o˗w @ܧf1g'iK9/{R; ز6<do wRwg +#AyJ۟&HU8,\lL -fbt0iDSΰz Qf?@+,x6g]|3 ؾ96i&U;脋@&M(`B&VqIpH êAR}ϳ}q &84Bׄ(ebc=ˏ"@6mZH{AP4eQ2"m1I̟$RWH5{6sShG=$쁵?` aAllTM?<$;ү(ElU~ X_T-  Z-u;芋,4aܦ*Y5y !!<͕-mJNؾcྟ!%` 6&0h [uH:[o a/˂^76F00u2ϒ& [wJ4ylX; 4ѭLv=+M?QQdT}8Me,IX͂B!U3gI0r(^A$ o"%gժ[pC JGt֍YuTз`6;w,Iuq0 2a%&\ Cƺylz uG ;dU`N]Rp.I (cQXkT4+<CfP\JfӨf?,$2q8{ $uܯ>~L=QCظn#'h>vh_APDf{v,0{kՆe[`ϖxKAob|,cUhby{,]u*  k(a 5mZYks~@pHS;>|e+2B.F$DV.:_JC#KIZs/&I˃c$ӂce-_<ӧ0@ٜPA2}ܿ>2;0UOt8+T#*U~C' WZuNA)ǏAPfX{l7Əui_a=5jBP/^dHпί8L40djphS+7cj5ѬpI|!cCc p0=PY<^cvl~E!hE0g#:\D )U4ILf5qq-;A|N"pG EIG(QsLpو rO[ v0` D!? H/ޓJZ@JCx?.DpOSN:\\(ur0)qZ)K B-W,[Y~㼟>~ ('d>/_5"m'|F$RS A?i+ we{mf/THX`8ҪK(y/}@=?Y;vE_ VBO=K-Z|ot~>>;{ u!5jÁD /#F;|Kнoo~}=!zO\<@@! gUJ[Zf7nѮ_>ӟ1 Z^ `9570fLz[ 8Ç όr&/?g|WgϜvwõ3 K_V1l wW]Ȟ]{A <{wyxC(ϩkҀ͚3+c#ZKA!`U˃򈖭*9)nIh9ۀH(>׆BP:MACm$hJ(.?d`KsK\lٶ.Q(iצlX\/.Tb\!No, r%fc;B,.v7IQ?`Ƕ]X=rLsw1@&9C\op<#%WGOAɒ%鍒3mOjj0^|>s$AVIPƅ?o~ͯєdXxSYx%g(Ÿ B(;&֬ ujbAEQr.I-HL>_ . H۷۷8>S"B2ypr \\\qCx̯׿2/"5kBbA(U p8s #IkVG3,W2nʾNJ R 7sȥKr 4hjي&9d8'8s BZ^ bıf ™ ;kp=x)3,,۰lٲ{=}cg/!(7/5ǎW2o$;3K @Vm@aNO`ݻ`77h7о#jo}3ZZuWcCcoO; < }a:_{MdjjB1.lժ5BD)Es1=zzOzz^Z D,Ɔ&JO(,):QLI9{\yqI#ch۶`= nC\V³g"0jfKZ 1#:xzB*^T\]D>֭__ Z4oĔ%8y'c, [:k"~AZ435g9:~SzfCoQ\\(g'7kXbP[6alٷQLb]z]>].]JY@'N6#'”)pUQnnn&-g)s Ҽ#XPYy H ?Pbwoܯ5m*N2n%3Us @c 024Lے>{p{0]v⑁%3] "(֮b2isNa`̠M[Ý),7XZt&Mh݀'%D CBV-[9j6oٶeԌY(,Ek" nPti +=)IG4ѭ{Ԧ]oPX$_7062 ,)S-`|m$ U`0PPP jѺyW/jRx̜Y@BzbӸ c9*%-FG#-:su >n]P:Zt3 nٚ<% Θ5%P0X`^n|[GNwJ˪2&f`anLp@E|X$f>#== @VVV6tB${0 c.&jҠ?QD`#TKֻk׮0qn+)F sKb'1"l×/Yen}SSQ @_|nӧ ;w-Tp֠uڗ/_ȀO |GD$#Cc@O9 ;;>˨TιuV-qX 8zXW 0A###c`ԁ1MQC1vnWphl޺L=dİ!&$5W LD}ڵc T <Գ{O4Я͛7[oIҟץp>ހTz A+ݷ/lÓ1[=?fO ]j^~$/6O0~-Ɇ6իW&%ǏqDP۾(||md?ϬES,RCCوHcs}?[o;?oי}[iջwIݯ{)5'{/)8*PWj5UԬQ ?޾y ߼f^W9O=e ! -\hc3(i>$567HBHIڷ%K#Ǎ0|֪gIÐ#ɇS( B073Kض) @ZZZ; ReJ}L;ҢaÆwd[f|1yˢwE?q={Ph BؠOTDT¶$ ֧ݻیBL¤ZjY ijj)Xwmhկɗ/^R BX,Y2ˆm]̒b~?nM+P B8xLrr8(]F ۛOEB7~gs''J=j.ہ6,Z$ 8رc9~]%z =v6v/X: |Hy]zA1&#+6=+}'IϚ %4f.0LW==渍1&PڶX;=}<o\?j lV˃---/#GNu@f".ڶ9r]Q|x$''ς;"Fh Ls=~܄ſ:/(ʩ:xyyqzzQ|0FݐE2u.7/-==RojkJ>gUiVyotH+fF gTfprZ8D|||M[;?y?ν#G\0tѻ)P6p?f/]0}E6R\7F)&FƇeO ? B .蝻pÙgM]8!'':u+B+W c#dcm۶=@aكԛ׽zRu{Y3M~sOڍ4nsQc uo4l𦎎] !";;5srUddj<jϳ^R9LǏ|yj~AӇO?~XT*ſW,[]re+T-]՚53k֨fY93U՚5j?K^^_ ܑW/dIENDB`(0` %9?9(.2.i$,' &! ' %!                       ! $" & #*#'-)4<8?484?#*$"#"!  !!!!!!!!!!!!!!!!!!!!!!!!   !#$ )$282V).+%!                                    !$(-(&-($                                   #%-(-50_$                       !             $.4.bUjj $!                   joly}z&-(                &!fff -30Y$             &-(Y^[MSO !            $.41R%*'"         284kolFLH  !           "$+& &!!       ?EA{}$+&           ! &!'    '-)UZW             !%"!! CIEZ_[ !          ! !  ptq"         !  9?:jnkFKG9?9,+2-o$*&% !(#!                        !&# &!$+'*1,r:      lpm#)%         !  !          X\Y.50         ! !!!  X]Z"       OTPkol           !!# 283$       #*%         #$!  V[X       .40         !$!(!#w{yy}{     5;7OTQ          #!(#.4.,#    !      afc>D@  !         #.44,$*&}$              !         $$*$}%+'u!#!! !!!!!!!!!!!!!!!!  !#"$*&w6=6!$*&x%!$#                 %&!#)%z4<4"PNG  IHDR\rfsRGBgAMA a(hIDATxxM} H F$JcA!!]"AB.H""$Z)ea(cQAyYxO {)ysgw/h13q܅{2^=uVy^|[Ϟ{(X . TX?+U<  CYr0eFX@`w!\2&$x`hhF a*3;tyO8@BуƍaI W"4 +5̟PE z;'{}BīHۓKL /_7-} nxB穦_ = 01kݹ{:A|Bҥ*3Fmķ+WN9 hؖ zG֭IU)&pˣ?CO *AA*k{+ڵ?#C{U)^h ؃[ׯP)RےAL6aQbbS[ T$`۴ G!=5U}d&IL AK[ ? I%VCM[8p) BCcHL{L ݿz F- r h\SOB#U@8"傦#7f&uPAS1ڹk,Ah;v0\" BȸvK7n߹MIFj72NO=IZ: YN zuٿ jPD W8yn޼A*UΞʳEEGQ>Ӷu;ճ71fM؞ 7$f䈑5J'00{:Br!(060/d_8O@s/3fs! Ct*U’PRe%¦-`9CT >]PBjT{2G V8ʕо;zd&. -EaTY᳀s܁)U TV=QTP@&f޼!X%gPFeN ^ ~j]I#Tg? uG*JgϞ#Cz;޾}K.3 k׿.rO7>իWD۷I kVڢ_'9% r@ˀG#Ǐ^#Fk^ׯ_C(ʍ T+_F/3͛e7 T䘪83x3iڟwh~@LZa}\d_^`Ͼ2 ,rL1`^(0mOS!>a=Z-51?S!m1+e3f$&V/RHQXtpDF-@Rwl~׼Ȁ~aDG`̆td_מf;ѯ KCP 6'r~=dOƐ9czoЀ0?NDz?8r(|sB^.+ }\xcr 6A%i!ݷX kr!#yáoWG^ C=O?YgDA 0x`nӦ] Me%Â0r9zl+ڸlަdff҃^n. ފz..UZNde6UuɒɁ`dw@^.xy'iSd;{8=xTIG0Oᄇ<Zoɟ]udܸq:(c@ r2kLYAoA6?X@d>Y7 6MleytuMo)Щc'ɯkegIGuQpVTR|Ӵ=KR af(}iI EFcdӡM붲uCw: 6f]drp]J, &@ٲz\rU4 lLUJr)SW0n$ך3o6D\N.斠jն9wY1ŹE*_1&|IE : ع&j~AnŲcI ^1l @ȇam#PY62nɟ[|[V(4FJc) mL4 '䥚~5P52UK$;lR1?Yg3omFz)TXTF Ϟ(K+hoi8e1f$ڤ)ɣQA(YTU2%1}Lhݲ .]N7HN{1Оr@Ju^Ǭ`eN`&%>V )[ƌ)RsJ@ j .,J'O}] ===HۺY{xǮ <: mvdx9X4t[vf޻{ %;|[Wd eЭ ]X<3Vǭ%;@ZLE/ >u]k@-7Q{ će cԱ$3{a2VVI[XS;Ta`51k&r39 mIJ ?փQ1LRqkʢ@oW7Ť-<~ &.=HXUS}=5mT[KJŋUm˔.#-< HC?'`b 8vpdٮ0C8yvb׬-=tk2$쾴JQKA!ܼytlMo¸2 ЈG#K!Љ4`LӧOޒ:@΀6BC4!ށ;,Gc?}ԩ$'7(hpӭ ǠOwT4 Zh1UUpmz]5MxIJ 5 j f |q V,] AӧRKȜ`3ʀ[]U6@,%4HŸ1DGjcp׃:B,҂UA :1Q:B(ض}+2i QY߼u3/+ E*`QvaXlNw%J0iGpie$ܥWeB-u6uY{@ $(PR6orʩIaz|0jMڳVT$^.W^:䖠U_=:D 9Xj|kb #=te&ȤW^Ac+Sa$ӧw_~ G>:2`Ip, 4m@#@z0 /mOҩaj4h߶@Y1 C=x942m̤S^}z#bŊöTCP]tm;yS TG!~ l 4~7^ܼ*;tJeǒ l-f ([Vvncv B#@^0mu9pp? 6P_~U+V3kmn 7Pl%KYWhټ Ŭy@Ha$ѧ~}P P g/v^~ؙY{|`k*y(,-%?2ΜEȞ)SV6{ {wke& +0kr P ݻc<o߾ժ~)X 9pLpԵ#_ ^M)P\yۘ06'hU@ZmnߢF,uuqj slUܱ0Vf ڀ'Ns.׫?+U?Lϟ=w׳O6uN0us|"27d,֞=]a/6M-F;) nݡ]Pr\}ǰiKLVvn{ >ɓ'Z~cAwF7`bސF3q~jYx3gπ $,ʷ: -6MlSl&OBuXƎh|ꂖWiE q=  :[,kőįKj?TS;nFC|-@Vǀr(Ulސe .lʹHҦu[9}]yM&|9]\uZƏ];\μ_6ŃAM@ zC1DaI2m?q wwB vsfU$BisK5}6Ǝ-y+V,E?iբ%50w=zD T* fZn70 OGuش)A~ذy@ dAmD\sָ{ @ܧfMabx}-=Z !wQYݵ\LQ =kVCЌi:>I'&E^1{:Ĭ^I {[{?WX><yҘhrha]d]a[ٔ[vM, veXv q5|X UX%%>#`4yl{_ Ta5a$\'!񤼂Ec^@ tR:hf_"BѨ7q 5~?Slg=$ x_jvְ `m\ 89;w{S#}A:nν7zzz=9yP57@VpvYi¸MUbĚB^H۹Fy"jY4n'hT]0KIoRgϝ\HX z_8_L`s&z_z|& kV4y֣+oШM6?St=)V8Q^H[֓0eL80 1C!?Ç~}=IXx~ȴS!N7m?S ;;bdhV2uTR&!5;"=Q@gXnS.Vb8 :nܸSV]vꂳ-u @.+Vh{>1+V˖DRJ"oU@KVʟfaz:YtaoOp.50Κ#cȿ[a}:fIr/(kpk- k), @*sgQRA!͛36ڊb1U!1!Y~Z懆h }K6;! S'E @d )]FeQ1*cM 6%'7̝?J1#$#b+ yx2K[:qɁh|$%1ڑF?5 HS9 0a{IX+"aET::fS=~髱zh$Y8<³$W53)=9p"v7>t CB%+Tk"Ŋ!N>5!O% K"bnc0dPQr},m5)SDH >OO I'cgɯq\c~ S:58 ~ҥ&rPA8/Qz:?LF]\)x _v57&+{6uhAS:R}q$@@pHJ$6xd3&Q[ lBhHuD?G&L6Jz]uw(ٱWsJv?Hz97`?dvOye{Z*ڣ"1$K|J-](Y~\_>idptx2P `V_)ރ5"{t2HR\9ؑ:"9#y,ۣޣGBdZU:"p\gd%](0`)Щc'HٖcJ_3{̜ +TG+m}05`uDeg'rdD1; :U@TlMLxwѷ1˛5iO׈gC"g-~MZժziК #f͒4R )9y](?٦q_Gc1+'L,5 J*{YԸgAʕ*D̸!6a^BqEd XG "|q{Ν?ρ_zǸ~?}J#! 7C5#ݱڟ׀^SWPխ/,EbWM/BBV7Wg*9hצ%Eh?Ss$ q;~-p!`[/wr/C^͛7$AP4 %tSps9 #+alq"`iCSXtu׭#x\B;9ۣUDtٖ:B ٶ}+_C`.%JzuC9==|ǏBy+WE*=$PD< AK0dP_7n FwfaÚx9ZBĒp{T[Cf>lx;ghD@ I'D93Ŗs;J#I}ӤDgdɒsnќYv ޣ4NCtd  'u6.Hq3Fyj4А0A Putjw!B"y!nm,L>Uc:M6{;~70 g? ~j@I?9L!T* LA$Yp8}4\*\rn߾ůs6xƍcf޲2 ܑq;#;1n-ELTN>} NΎ:oIе<[lRJh 'æ- $v`gkی꫙H#G{QGOr)g f%jǡ׭GJQEBVMN2nTs @c׾䩓j`ia @'W> LMMgr<XIXե<~;cWoF1po uP@&v ǿ{u=W& pq PH{d^@P:{fMlj{pw鬘H>z@#:@;[;~g}`ŦA‘TK(&c7ȃKIIa @Sf`-?Geݘ!u6  -6ֶ`og/p@|8LTa id#2GLX"uJ3`ͺ8 f?B% 9 @-ӧ^VSvŘׯa̸ѐs ڹ̛=/ge2d9E+?GZ2G ?": YZXx!7a,ܻI1ŋ /~]zvCP X6MS˔)gQ"Bah2ޮuefhSF&z6&Cp՗xE0leʠ00p>`؜TtE~W<»%i3ĮY o\'2 M 1!)w@O0/Ɇ\~?hݽ=-^zEH_ @F {j^:| o-"#ݸy_8/f=gΞ _~58Pf ʗAl'|jK>7Mx9<}q6:ϙMrw A( ~fƴZ_5jp/Du`@~Yʢ^'0d;_:  eЭ3̙X|vݚ) 8sL5ܥ!%}@i0Ta AhOVDA>sq+<{LML`s|1+ZQcص4 `=-6Ξ=CO /yXX7ndܤ'JRŊp ǧk'L9WFN]q)[J]P νZ) 7nJ$"t ϷdA=χKM$Sn=HMڮ"Cfs '~;NFSp07Uo*MHw7czRpahlj1q_*Ml??Kp}6}dϟ/Ykծ #5n                       Y]Z           ;A=                                 &!              !                                                                      #                                                                                        GLH                                                                       "            FKG                                         픗                           !                                                                                                                     173             fjg                                    )0+                       4:6                              =C@                  PUQ                 INJ                                                              062BHD                                                               AFB                                          KPLgkh                                        fjg                                                                      ( @                                                 +2-                                           LQN;A=                                  psp                         173           =C>                                                              퐓                 6<8                                                                                                                                             ink                                                    mqn                                  JPL                                                                                               퇊                        퍐                     _c`               fjg                                          &                           &                       RWRPNG  IHDR\rfsRGBgAMA a&JIDATx]}վ6KئVBm@qvkкQ[QcRl}kK1JBuS@GЀ@-)$bjT4jJj FLs׻wg~3s܏97!粻HxZ{wyG3'O|裏ĩS{O|ⓟdg?ˎ7N?^,[,=~0W_?Dr>ψ;OL8QG WoookϞ=/* ?/:;;# /7|}3EB@1cFVBPj{ytUn ɳēO>1(o7$ͤ|p++#2*xݭ<}g-5:;;[y"Toz,HsU/ؿTK8D4,mMp ɜ9sZӟ,K]SN[n* (yGDY"褭nZ1aSYY5"PHN8hӳdikn}BJϊϒeq 5kY=K\a %Kկ$Xd,#Jd2ڰ DTf?d/8h(zzzZjQK,L~{?N=,Y/ ,dvvȑ#ЮRdIE svm%ʯdx@DaϒeW 21m+dɊO;E*#?KJ&Mj%%{ ĨQgtqq!?9)l_=* ~ aKPU:?ázr=zTlٲEڵkhx ߿0 P$bC'_zŗ{mo3Di.@@U?2S)Ds=L[o{a3hr1˂?},sNkv@5ĭ2G&?NAKƍ+|>wrko9< ĒWˀ}Yg'w3aAs9sP3x"qL]rVk*b;Qh`je`,_t eUW]%Ԧ3V~wTc"@*kSkFȹMg4-[AE~'_$ݪU{kEe~3@ `wK2*Q_ H4? zy &s2x%8@d,j48P~5Y3~js.ڹuwiR/K9$@Ռ (vi twws&Ў"{+W'Ν $ud !5ݟ8q",:H:>PM7?k}gW'% cG* s|~ߊ{キP^?& `OB9s$qF30ݣ5uһ{ӞGj;vL̞={ˑ?]Q*@ ShW :@kiREfɒ%@ n?F̀,ui{?NMEYm޾}{K3HT,'y\.B<.C+Wr7ӂEn>5  @}5aRQB i ڎ04TrY0w\?XKÇ)SˀM;.jKV|E,W|!좟3k\x{"9w-k VѣG̙3sUJ],X@ IM9'05X@3.^!A^`2#Z%((t@V~Xӧ|$"c߮v\R3p :ӷZe {Al`,pJdۢꫯ.<4`T+\sc8UJ% ʗjY R1nT%eH̛7/w7P)HEE 5w2| .62TT~{zzgY /25UEa{3TVO[\ Ow*gQB 57 l(Pʀ Xd"n VElC[/@ʒ^d Ai7a/5\Thl*?<&yZ+Y5V~W^yt _p:1ڶ\=F^C=lf:o78IimU@z>H0vjC3f+W$Թʕ P3m'5kBa-fed I,@P|gSS,Y2 Au[E݀,ઃ@YB ƕK#`zV#PVbܛǓz- )V}X@@V>R&/^\ H~|۵kW@<zd k PK֠  L6-@3uxĉŸq?yB:uB>|88pՖWfYT9<|Aח:Tu'7tS%ᪿ-}rUX-cY [Eoٳg'Xw}w|0NTs$ `au]#*O8!z衡 ^K az'JuaXRאA@x`,WcGQLZ*kYP䰩3LʃӆVlcgr@STXR@m,QWIE Hvl@e*>bԨ\&*6:DԒ ,x 9&g-vIOTl" "K)isuY+@y͛QXo.m"E@6ʦt&Qm6rJJ߯Z+PKN@S+@m5B.VGl 6X*X)bZz]wu$/AMR6'X`W;w5W3QET,LV+L̲TacM!MtVT֣4f2՞!#`BscLHu}2M/ d ![4`h4M}p>\dME!t*ATXӫ$]@y6մ+Py*J@S_t2e Pb Ij)꺅ЬU떤 314٪@@Baʟ-Y'K +bZgDI-H1P6q^^_r+  sXNB 2,dt;v8SN"d cuuuXp7 yXe-_V(v|*ni C}vCA(+BSӔ\"T  B$W`>A $ MH; "% $˴G@Wz+惓%!ZN},m g?$}&{֭ "}NAu+)=R lYuKKܫEw TOR2VxcxKA.\0LK2)2!! &OM~}yI-r3|>`r=UMXj#Z%UP~1ZLͱA @E)킀u˗_!:*)?PyT FX }0lWPQG7 ^(zI@Ք E3=z4)m\P4"F]@h0KqÆ ʏsucjK1**ct_OfHE4+7,{ !}P5pjٛPeƒgFLL ȞۺNL=^_L@Օ_C%uL R u/lZ*P^oG(z6^~T7 :XAٲeB!jrV3j p=+FTNÕ҇}rlL)Az-T~,NWM5 NʟƢc G&CG~jcǎNNQ<4Е߶ο/`oGJF0PP,K@E8V4K*)mw˽``>h_U3^Az϶`Da*ʶ.*dZ/_j d=)SćY[N[ĩuVJ&QdH-(=2h;œ 3)+*LAݚkmާ.A$$e#>}y={$￧ts=\uZp~g,n^ 4W)R H-Ozլv-dw pb$i <.~@S_6ըm.\QЂ=pDw-&+xfͺқ›H1Ph킸CƲ(O7E{4h \J:<ܻ+QЬ#fΜieq#k~^}7y7N6#dKfGKof  T \Rvϛ70IYE*u>bA [+sBGMū)[Ai@Vb& +|nuX+V/^x TPSMrHY 5d{  cƌ?O6\_$z)єg`,}Cާ؊|G}۟oM`x5pH+%fթX?lzf̚ `.Qܬ~ڵqFഽf~ q f]K@Rd;}EԷ"[D7-ba& ,I=> I,.Ah VNJi(ozdp:9cX˓-"MĊG\@w o'qn_ouM@Ccf+ysdF}6@DžE!S46IYg r#ӑI=z4Yg%."q饗f2gڪR9@cI*iVr&i֯5duQژv(ǵr Gg.ʙYdž5YC|ZyPw__+rڜuwɹqtjnrUCq H ce>'o/$a'cP=j':[S~ّ2WVz1C@,Ur`Vlt]^mwr s/wމ&VgtLZ=$Ɲ\mw81\l ,Q/m6wrR2JT:e^xdUg`.ǨfQftfpBEe`ѓ-_` شiS0{rUn@DEEU_wyg"T1)?+ 5 T=ڔN?J*P]h7{ƍmq,ܲe + SI>PVmHLE0V Dޣh?d DOŶTJ4UV>裬D5 H9\0WRUotRο(G( @ (CzՑ| 2yd'Er4U2bE,oH @ :s@;v,~($ 1* 5vx}Խ.ᒡV+UL6?N%|H/Rbz0h*ӂ'gd MAR>w +Tz/f 7 -2&b { r"VU~KR~c wKmW^-~ >k֕\ϔ6^tܹ3v#Vz/b۶MbԨZ኿:}n{!(ƍСCΑUwX'F F1,$ !?r:E E$R:ԙX+ Fr %^|Eo?JD<:u*5A3_ $9r$ƍ]v7_^ķ~^}"J Vɳ=m_z+Vp +ZZ}]1uh\U0$>|xH2>u^\|x_V&n /xt_^E,@F1P9JȺaG?q>??;48qbHs:XN%]_&:@ &Mj*Z%G_r%1藿;Vۚw-Ľ!Y4_M,dVPPY7iS;/I^A@УH5l)~;@]@# ӧ_Ι1lҲSP?c -}m%l!=v`>~ҥC$ќ9sZ䓢8m!0mڴ`p dF)G]A 3Ђ…^<'m#}.Dv[- T`ڴa3E1̣. -[gb8 '̙3/὿=XUDIYȑ#QMV5`(n!8c q饗fM<%y?$cGM. F@U& r5M"S) ,UYVǏo[4(rM@+9@WB_@y>| @;q+"v/WLnS__U *py}D09x`i:.CR܀S}n&n7T(sL  ;&nF!WM. Pl|"8 LxY$;/|$1(iAKN@໓<7 8hK,w&SҪxbP@BP~ƥٳgVFKki%@J?vJ)Rå5k*WuAd T~jD=|'7jRArdHՈ2T~D2b3@P!oQG{_ˍ'jUAO緊] ;ǏC=߯%@%oVlٲP$6.(vZ'zpZ1(3@%P'姶$ DKBg֭bӦM䬙Uj F; R(̀" ?|qH(ݻwx=y}ow $Z"IݕV#~n„ sύy[ u_o'^{Ԩ>UVGAq}AD.芇qŮ%A?_W(.w}i{:_ϖ@sr ru!* &488hm” `ڽs v %/ͷ=6O.6lPtPT +@;@iUpP*Y e[ ^A@e!֛oiMX|Η}QQ11cƈB*dg.W /zP*ri U~cyXYR?}gJ KXCeF ,C*XyYt?Юs8˺XTFa>b!X&?uZrd3K)v в]$tAt NWءh^XV1&Om; Z-D|޺ @.. 2[{U"U0]暑|\">, Y}VZzcFYv X.i2ij z#j %KBH^xꫯ|pY:o"Tދ/뛾r@Kwx ,CUV~|x" Yrf/.XrTyd| k'O|?SNgW3 8㌡Ƨ>xhqYgn6**IENDB`(0` ʦ @ ` @@ @@@`@@@@`` `@`````` @` @` @` @`@@ @@@`@@@@@ @ @ @@ `@ @ @ @ @@@@ @@@@@`@@@@@@@@@`@` @`@@``@`@`@`@`@@ @@@`@@@@@@ @@@`@@@@@@ @@@`@@@@@@ @@@`@@@@ @` @ ` @@ @@@`@@@@`` `@`````` @` @` @` @` @` @ ` @@ @@@`@@@@`` `@`````` @` @` @`IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIRIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII[IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII[IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII[IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIRIIIIIIIIIIIIIIIIRIIIIIIIIIIIIIIIIIII[[IIIIIIIIIIIIII[IIIIII[IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIRIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIRIIIIIIIIIIIIIIIIIIIIIII[IIIIIIIIIIIRIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIRIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIRIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIRIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIRIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIRIIIIIIIIIIIIIIIIII[IIIIIIIIIIIIIIIIIRIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIRRIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIRIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIRIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII( @ʦ @ ` @@ @@@`@@@@`` `@`````` @` @` @` @`@@ @@@`@@@@@ @ @ @@ `@ @ @ @ @@@@ @@@@@`@@@@@@@@@`@` @`@@``@`@`@`@`@@ @@@`@@@@@@ @@@`@@@@@@ @@@`@@@@@@ @@@`@@@@ @` @ ` @@ @@@`@@@@`` `@`````` @` @` @` @` @` @ ` @@ @@@`@@@@`` `@`````` @` @` @`IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIRIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIRRIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIRIIIIIIIIIIIRIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIRIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIRIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII[IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII[././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685599.0 tryton-7.0.24/tryton/data/pixmaps/tryton/tryton.svg0000644000175000017500000000310314517761237020635 0ustar00cedced ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1745680285.1974444 tryton-7.0.24/tryton/data/sounds/0000755000175000017500000000000015003173635015041 5ustar00cedced././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685599.0 tryton-7.0.24/tryton/data/sounds/LICENSE0000644000175000017500000004433214517761237016066 0ustar00cedcedAttribution 4.0 International ======================================================================= Creative Commons Corporation ("Creative Commons") is not a law firm and does not provide legal services or legal advice. Distribution of Creative Commons public licenses does not create a lawyer-client or other relationship. Creative Commons makes its licenses and related information available on an "as-is" basis. Creative Commons gives no warranties regarding its licenses, any material licensed under their terms and conditions, or any related information. Creative Commons disclaims all liability for damages resulting from their use to the fullest extent possible. Using Creative Commons Public Licenses Creative Commons public licenses provide a standard set of terms and conditions that creators and other rights holders may use to share original works of authorship and other material subject to copyright and certain other rights specified in the public license below. The following considerations are for informational purposes only, are not exhaustive, and do not form part of our licenses. Considerations for licensors: Our public licenses are intended for use by those authorized to give the public permission to use material in ways otherwise restricted by copyright and certain other rights. Our licenses are irrevocable. Licensors should read and understand the terms and conditions of the license they choose before applying it. Licensors should also secure all rights necessary before applying our licenses so that the public can reuse the material as expected. Licensors should clearly mark any material not subject to the license. This includes other CC- licensed material, or material used under an exception or limitation to copyright. More considerations for licensors: wiki.creativecommons.org/Considerations_for_licensors Considerations for the public: By using one of our public licenses, a licensor grants the public permission to use the licensed material under specified terms and conditions. If the licensor's permission is not necessary for any reason--for example, because of any applicable exception or limitation to copyright--then that use is not regulated by the license. Our licenses grant only permissions under copyright and certain other rights that a licensor has authority to grant. Use of the licensed material may still be restricted for other reasons, including because others have copyright or other rights in the material. A licensor may make special requests, such as asking that all changes be marked or described. Although not required by our licenses, you are encouraged to respect those requests where reasonable. More considerations for the public: wiki.creativecommons.org/Considerations_for_licensees ======================================================================= Creative Commons Attribution 4.0 International Public License By exercising the Licensed Rights (defined below), You accept and agree to be bound by the terms and conditions of this Creative Commons Attribution 4.0 International Public License ("Public License"). To the extent this Public License may be interpreted as a contract, You are granted the Licensed Rights in consideration of Your acceptance of these terms and conditions, and the Licensor grants You such rights in consideration of benefits the Licensor receives from making the Licensed Material available under these terms and conditions. Section 1 -- Definitions. a. Adapted Material means material subject to Copyright and Similar Rights that is derived from or based upon the Licensed Material and in which the Licensed Material is translated, altered, arranged, transformed, or otherwise modified in a manner requiring permission under the Copyright and Similar Rights held by the Licensor. For purposes of this Public License, where the Licensed Material is a musical work, performance, or sound recording, Adapted Material is always produced where the Licensed Material is synched in timed relation with a moving image. b. Adapter's License means the license You apply to Your Copyright and Similar Rights in Your contributions to Adapted Material in accordance with the terms and conditions of this Public License. c. Copyright and Similar Rights means copyright and/or similar rights closely related to copyright including, without limitation, performance, broadcast, sound recording, and Sui Generis Database Rights, without regard to how the rights are labeled or categorized. For purposes of this Public License, the rights specified in Section 2(b)(1)-(2) are not Copyright and Similar Rights. d. Effective Technological Measures means those measures that, in the absence of proper authority, may not be circumvented under laws fulfilling obligations under Article 11 of the WIPO Copyright Treaty adopted on December 20, 1996, and/or similar international agreements. e. Exceptions and Limitations means fair use, fair dealing, and/or any other exception or limitation to Copyright and Similar Rights that applies to Your use of the Licensed Material. f. Licensed Material means the artistic or literary work, database, or other material to which the Licensor applied this Public License. g. Licensed Rights means the rights granted to You subject to the terms and conditions of this Public License, which are limited to all Copyright and Similar Rights that apply to Your use of the Licensed Material and that the Licensor has authority to license. h. Licensor means the individual(s) or entity(ies) granting rights under this Public License. i. Share means to provide material to the public by any means or process that requires permission under the Licensed Rights, such as reproduction, public display, public performance, distribution, dissemination, communication, or importation, and to make material available to the public including in ways that members of the public may access the material from a place and at a time individually chosen by them. j. Sui Generis Database Rights means rights other than copyright resulting from Directive 96/9/EC of the European Parliament and of the Council of 11 March 1996 on the legal protection of databases, as amended and/or succeeded, as well as other essentially equivalent rights anywhere in the world. k. You means the individual or entity exercising the Licensed Rights under this Public License. Your has a corresponding meaning. Section 2 -- Scope. a. License grant. 1. Subject to the terms and conditions of this Public License, the Licensor hereby grants You a worldwide, royalty-free, non-sublicensable, non-exclusive, irrevocable license to exercise the Licensed Rights in the Licensed Material to: a. reproduce and Share the Licensed Material, in whole or in part; and b. produce, reproduce, and Share Adapted Material. 2. Exceptions and Limitations. For the avoidance of doubt, where Exceptions and Limitations apply to Your use, this Public License does not apply, and You do not need to comply with its terms and conditions. 3. Term. The term of this Public License is specified in Section 6(a). 4. Media and formats; technical modifications allowed. The Licensor authorizes You to exercise the Licensed Rights in all media and formats whether now known or hereafter created, and to make technical modifications necessary to do so. The Licensor waives and/or agrees not to assert any right or authority to forbid You from making technical modifications necessary to exercise the Licensed Rights, including technical modifications necessary to circumvent Effective Technological Measures. For purposes of this Public License, simply making modifications authorized by this Section 2(a) (4) never produces Adapted Material. 5. Downstream recipients. a. Offer from the Licensor -- Licensed Material. Every recipient of the Licensed Material automatically receives an offer from the Licensor to exercise the Licensed Rights under the terms and conditions of this Public License. b. No downstream restrictions. You may not offer or impose any additional or different terms or conditions on, or apply any Effective Technological Measures to, the Licensed Material if doing so restricts exercise of the Licensed Rights by any recipient of the Licensed Material. 6. No endorsement. Nothing in this Public License constitutes or may be construed as permission to assert or imply that You are, or that Your use of the Licensed Material is, connected with, or sponsored, endorsed, or granted official status by, the Licensor or others designated to receive attribution as provided in Section 3(a)(1)(A)(i). b. Other rights. 1. Moral rights, such as the right of integrity, are not licensed under this Public License, nor are publicity, privacy, and/or other similar personality rights; however, to the extent possible, the Licensor waives and/or agrees not to assert any such rights held by the Licensor to the limited extent necessary to allow You to exercise the Licensed Rights, but not otherwise. 2. Patent and trademark rights are not licensed under this Public License. 3. To the extent possible, the Licensor waives any right to collect royalties from You for the exercise of the Licensed Rights, whether directly or through a collecting society under any voluntary or waivable statutory or compulsory licensing scheme. In all other cases the Licensor expressly reserves any right to collect such royalties. Section 3 -- License Conditions. Your exercise of the Licensed Rights is expressly made subject to the following conditions. a. Attribution. 1. If You Share the Licensed Material (including in modified form), You must: a. retain the following if it is supplied by the Licensor with the Licensed Material: i. identification of the creator(s) of the Licensed Material and any others designated to receive attribution, in any reasonable manner requested by the Licensor (including by pseudonym if designated); ii. a copyright notice; iii. a notice that refers to this Public License; iv. a notice that refers to the disclaimer of warranties; v. a URI or hyperlink to the Licensed Material to the extent reasonably practicable; b. indicate if You modified the Licensed Material and retain an indication of any previous modifications; and c. indicate the Licensed Material is licensed under this Public License, and include the text of, or the URI or hyperlink to, this Public License. 2. You may satisfy the conditions in Section 3(a)(1) in any reasonable manner based on the medium, means, and context in which You Share the Licensed Material. For example, it may be reasonable to satisfy the conditions by providing a URI or hyperlink to a resource that includes the required information. 3. If requested by the Licensor, You must remove any of the information required by Section 3(a)(1)(A) to the extent reasonably practicable. 4. If You Share Adapted Material You produce, the Adapter's License You apply must not prevent recipients of the Adapted Material from complying with this Public License. Section 4 -- Sui Generis Database Rights. Where the Licensed Rights include Sui Generis Database Rights that apply to Your use of the Licensed Material: a. for the avoidance of doubt, Section 2(a)(1) grants You the right to extract, reuse, reproduce, and Share all or a substantial portion of the contents of the database; b. if You include all or a substantial portion of the database contents in a database in which You have Sui Generis Database Rights, then the database in which You have Sui Generis Database Rights (but not its individual contents) is Adapted Material; and c. You must comply with the conditions in Section 3(a) if You Share all or a substantial portion of the contents of the database. For the avoidance of doubt, this Section 4 supplements and does not replace Your obligations under this Public License where the Licensed Rights include other Copyright and Similar Rights. Section 5 -- Disclaimer of Warranties and Limitation of Liability. a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS, IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION, WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS, ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU. b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION, NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT, INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES, COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR IN PART, THIS LIMITATION MAY NOT APPLY TO YOU. c. The disclaimer of warranties and limitation of liability provided above shall be interpreted in a manner that, to the extent possible, most closely approximates an absolute disclaimer and waiver of all liability. Section 6 -- Term and Termination. a. This Public License applies for the term of the Copyright and Similar Rights licensed here. However, if You fail to comply with this Public License, then Your rights under this Public License terminate automatically. b. Where Your right to use the Licensed Material has terminated under Section 6(a), it reinstates: 1. automatically as of the date the violation is cured, provided it is cured within 30 days of Your discovery of the violation; or 2. upon express reinstatement by the Licensor. For the avoidance of doubt, this Section 6(b) does not affect any right the Licensor may have to seek remedies for Your violations of this Public License. c. For the avoidance of doubt, the Licensor may also offer the Licensed Material under separate terms or conditions or stop distributing the Licensed Material at any time; however, doing so will not terminate this Public License. d. Sections 1, 5, 6, 7, and 8 survive termination of this Public License. Section 7 -- Other Terms and Conditions. a. The Licensor shall not be bound by any additional or different terms or conditions communicated by You unless expressly agreed. b. Any arrangements, understandings, or agreements regarding the Licensed Material not stated herein are separate from and independent of the terms and conditions of this Public License. Section 8 -- Interpretation. a. For the avoidance of doubt, this Public License does not, and shall not be interpreted to, reduce, limit, restrict, or impose conditions on any use of the Licensed Material that could lawfully be made without permission under this Public License. b. To the extent possible, if any provision of this Public License is deemed unenforceable, it shall be automatically reformed to the minimum extent necessary to make it enforceable. If the provision cannot be reformed, it shall be severed from this Public License without affecting the enforceability of the remaining terms and conditions. c. No term or condition of this Public License will be waived and no failure to comply consented to unless expressly agreed to by the Licensor. d. Nothing in this Public License constitutes or may be interpreted as a limitation upon, or waiver of, any privileges and immunities that apply to the Licensor or You, including from the legal processes of any jurisdiction or authority. ======================================================================= Creative Commons is not a party to its public licenses. Notwithstanding, Creative Commons may elect to apply one of its public licenses to material it publishes and in those instances will be considered the “Licensor.” The text of the Creative Commons public licenses is dedicated to the public domain under the CC0 Public Domain Dedication. Except for the limited purpose of indicating that material is shared under a Creative Commons public license or as otherwise permitted by the Creative Commons policies published at creativecommons.org/policies, Creative Commons does not authorize the use of the trademark "Creative Commons" or any other trademark or logo of Creative Commons without its prior written consent including, without limitation, in connection with any unauthorized modifications to any of its public licenses or any other arrangements, understandings, or agreements concerning use of licensed material. For the avoidance of doubt, this paragraph does not form part of the public licenses. Creative Commons may be contacted at creativecommons.org. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685599.0 tryton-7.0.24/tryton/data/sounds/danger.wav0000644000175000017500000006114614517761237017042 0ustar00cedcedRIFF^bWAVEfmt DXdata:b2,:K4Kz/$e!* a>} %y|)VGry3 A,'3rR G\4Sz7yi ] Ro-p P^|TA`}5C=_apS`8-^~1P *lG.cvWt nr# %')/3FRi|mM0SY^G;L!z+* W%AfV4t]C.2v:tWqj$}*@1lq'8x]e,~!%^#?xVgO'NF| ;o߄ݜؙR 1j+ށ3ۈڏ6|^PKhمѶΫ̆oyԛӀf͍y$ȪNjbʍɤbTJbhbod2U;h%+Xſѿ>w{A& 8UœėjìRNňʿ\ǡ° 9CҎ->:4*sړ]bhX   qz P7[(p`h -"&(+(%9!  vVyA#6g5 oMaJ]ZL߃"CN:`T Z,+Jq GߡmrSU8G ]>mg_:LeAS/z?(YqcK!vycKtC"&Cu uf 04cWu0&?1N~qr6CyyXozX`+[2% :NK 'X3U[FIN hFtz[mn0?c?{B.V-Q)=(Q *T]slݵؿ "+[ӁU JeήGđ#F+ <`% VN-#\+ +%#&4#,V,p:܄U#{(18<=<7a0X& = ɫyԶNA =ƼǼyMȬ}wU "K&%!jpG~Eђ(u8 N%c)*'"/&s?aԍk*5&8 0%@ wܽՀЎ) #\$i! L$>czK@; JP%y)+,/+(M# EK|U .1- [E CD) %' # ] t TsE2=]] P8 # $:tU EL4oedc [atkP6z= (S 6R/yt1H`"muNDN4W}OHk1~|l<[yOT*g{)N~ ^Ym_{-/Wivyxbk;VVH bN1Raq !ea},֧D.A]G9ئҦӻ՘֪ y!PЄs kOsɝȳKADE͈ͳͯkͪ?p˱e7;ʦun]U*Ի>y|+δoFMҏԓ׈QW׹hKF؞Wp|[oBCh #Y Ӫ_$TYJSu C c g5 ԗȸÚ/Œ%+d Mm 9,D Bmxcs s_> נ K(/46e4/()xֽa ENBt^=zIÀʒL. iG0 - 4|>Cʃ] 5KdCc jiӚ_(/>4n573=.&ٰ;Ήq;3 D rk KRQmCA$#1s P@liGm 2  7LU%`v1TNuF9|E]|~CJuT}Oj":HEj}/n6wC*^<c(c/J4663z.&,3_>ԁЉC^BLPȮxOĩIpRQ lJLJ՝Tm"$# 7p zVN|77 q> )+1S68h73 -$Hm0(ݢغzWev NKMH? {b6I1 E-!$%%w#S6fa?pN1 Ofu  3H)l qu V X}k^?= ?* 8|i>"buR.!sCM;9><?R!heJx6X\ZJ^ 7|jIL o} ;.pFIxt_xw-qr0ysygV$n4q"M^Mn-q%E BKbwlJm>Zߐm"cgYMa۷VWڱLܷ3mrP' Hf݀udFOݤTzm64$G-  $i".v]jtWd/9uQ};~ݧZ~  NO(VMع^ЬεՒ 9I# / 8!.{`Wd Zd[p5 d[$+&0A331I-`&R )۝!Ѫ\*o[ LNd{}œqҫڅ0^ 5X!?m Ro`ے׌( 40! o Lu6C(@&.S4d7774'.<&~svܮkjg $V S tK^yre Q|! ;GFC8 ]>% JTu pMj.S\ (&M?@vA ^ > x?Q3to?P\u=7(=mgo_7&}Zl96MHMyn-xg3?Qw. (s^7/Wvg)2#l6L1|k~5kxo}# y1_}vex8}[X0YZ<`:T1(f[H5 )Fs'Q6 :Z&$Q JWufpn/pkrQf=?]Z_^g !1i  ; #S" O X"| sd(: K7 ^ z 2Z@oR s7lX' BmY-Dj4  iY""6-a)Jtl 1 VY  <B^z*~H. 8 /&( m |9^S6`s<` -b@#v`fB-_toO$< 2$eh~b2<SMKeIe`/>Tw{"u%5U@Wl&dI|D;mkm{ Q.@] Ea&SF,sYniq #+%4/JRk}jK/RRU|H8R'~ *-U"=^NA [B IjnZQ*/<}kc5O/Z9 l2|/m] :e/~L+suRktdb| ج8Gs7',Ud#ۂڕIۖuX?ގ$CՌъΒ̖цӃԖb8]aȪǙoɃɍNK"JheeĵX \4Yپp$+UĿп?M8=R ʼnÒi÷aaŢʳ<Dž·=x x Ӡ%*,ϵMȣŵԹ6C/t,KѾ Ba4aI- ]3! 'r7;TV+ ԭ($.$IcaURqsf ?  <Ԗи,W= #%U/H6::68l2)׹Hľ%!lTD=φM/El'"$FV!o"a~jĪUٶO ttO$!(>($1 \n<*ڷQuJ)3:6>>:4"*ϺAŇʡ-EB  F8z@  #&((\% krl <  {"v4]3߁,OX3=ayvW1!{`$s$Dslm V CgWL#iHs=$'Gx~p#/xyOI 1$6+Cx)eos3Xn4YdHa |0451D xl:_&^D9Be LEvKU 1N~x~\JUnأA9/+Oݘ"֪vZXpְM(U ֆW6XТpІ ṟ1K&ȳ7˒ͧ,\*϶[ͪ{ ˫vnʤ3_ԡּ؁9&Ҏж(ڜP֣ԀLя3(ٱJ^ҡWR݃@H1ݡֶVZՖ^ V+P @!Xx%[ßЙwt ~bH ^F{ws Y%&"tn ;ܷMud<)2+9<=;+7/% BTοȨ\_Wާ!e]E_f~]o]\v,"f&%!(#P9 ҬH̭͛>  &)*'W"OwZ%ӎ؅^h+5<@/@a=7a/$U jVWpG, #C$!a' Ct3< * %),,*'", x.j /= Da %Lb+\56 . T [ g" e7<]%d Jl=Bh\+pxs|*d6ng:%]?M-wrm:U lwXs]Vb=XwOF_$]#1J8y?hdROI5&*qqA|F5yMW4|q]21D7T FD[Eeka`Rz0}=ե.%]d / |ҵդ֡ZWЃr\8ZəȺ\PXS͍Ͷͩi ͦ6kˮ_5@ʮʍ̑~xI?XYΟ|X2qҺԭ؉E@יյS_/$Ճہߤ@E$A&?Ыqؖޓ_*[ܵҩK׮d F > do xaژ+;vq'3!IFm rF T\6s3{ Msc9 Qܼ=j i(W046.4y/V(U F ɪ2Cʷo޽@OhO3&Z"M. k B^dT#:ҏax  ]; M x#A{{`o<V(0r4_52-&S,O΅pG_8 5rR5H#6cs<6 {u>>Et4 H x< quPoV$x$Sl/pB7# vt;gjN{1e+T@Hxt r'm.uPhb#^ e91 H +U^+]f_I7cEBBBOSf} =7jWeONx92lE!&zbxOKs# /mnQ/~gd ښ٭"}4GO@۱؞پEܪڌXר~qؔؾ؍ֱOҹЂКэ>Ӹ0>SgԓԷԶaӐzuaV;ހa߈0zً؏Y$odg۔ڱpkC|.hیF2C%0{0'۝TB o K,6V޴ҝ͠JɰdҤUrx n` p%~  !iR B+ \ (/46~6t3.`&Sj YГPѥ^bWtZSU׊qjѢ:Ldb/:t\ӃlX "$# 3 *޾!zܫ 4 G*168:7&3,%$H+zkذ֣׼~7E u;tF!1 S,]}T x"$&|%D#Fj< dn[Q /RCdttN@&  F nE`i G n`/ *5#$Tc=kDgB1,*/A(lk P}v0RTWK`C n?Bw z2/uJ~,iws89m XwTh ]BRt]R:y!2uguv}Q%QCj9c e(rlD5< ecAF{b)V[imx -ta,R0\ߕsVSG:SۯR[ڹY7pnQ! "JiubDSݬfޑ!6o B0%=S `*H9?!30AG5V'|ݵ-fK*  Tr4Кxܵ yk 5X 1~fT MKJK) )$+o0b331,%(;ZXqD(Ҷ֌ܳp7KmcMۃ;ȥŖ[N< nj\ iזLRP&9t_ 5Nl=! /}, ?"j#x7i/v4y'/4773-%6=mlۗ AQI#\t3  h5$  a:m> AM %@ sN#l&5O:y,Q?c1 s s on`Q[u>5,cv6qAtQd<1UPQ|i+t c0@['*} `a>1[wb*BEr^3%;; ECN}6J37>M6P8q+6> =YHP \;"!nCl6v7M,|hCnwTUwN|hL`$yYd@"]P.v i>U)vadWA*Iu2eA2.O0-N~F_u~9kQsi/^s@Ui/ !$B XzZ;?IR0s f Y MxTy sRc4l B .#* wOuu 3/:nr$3&w@ ]m U J@X )Q*nd>- ^ P/ C RHCOz ` oS6 v]h Q5Y ucyfGVs{CClT :o6HRsY~% ~R\~,}"|uduANY8HpVuGSzmKvrVLnlDP% _ [q";@04bk `l$*'07EWj~hJ*L HKwF8U.*+N 9VEF]<Pg_. (EBU?nRHGWRnha+3gz4Z/ WuHC7W2IO@@]XnݖQ,'(#($,+ NҾ˦ʹڌ;{Mw*4';Q>=O:l3Y)][-'K''b@ 7_AXX +@4}5c#,'('%s %) ` \Xv]*|Mxh\[kn:%%"dujiuc5zAW72^bk +s5WPC* qBk?&K|x'.nuY6?4.|)<n|7mqk'%>Q@o2an,?0< (lT/ b wk{J xbh \Yl\ oM@m3~f{vUW5SՉkfmԇPAhұR6^ФfpQ̖D(O˫ʹ3Z$ϭQ͛q˥tpʪA-z! պl oз C6ڡ;|_FѠ]i3-6\|!ۥםұ҈ԭemk@+E֍uҫCd +Dԋ_6ɗdK [vT?#Kd'I!%%"xB}l7ݴ 8- B* 39==;6*/$! t̀Ȣƞͱ@Q,Z֫lQ(/?EW6N#{&%!NGء~Hѥz d&))p'!0YqܗֶӾd%tJ1,66<#@@=F7.#l 229ۃk $($  aaX"q,O= . %)',u,*'"f '*M5 <eO  N LT ADxP ifK : G I Yz +b Hn! nj-$k t.~{mGq".zZW9)`BM)pie?_v^GfkmmQ`zE~C~RP \IS(fG&iDfL]_@z`'Xc~l~cg>NU2>ACn'ykT~nFpPi5:՝+^\ޕ>\ըլ֗ոF [Єn I#ιCȖjcjb͎ͻͣf͝.`˥[5Cʹʡ̴ΡИh=ײ80мΘ~uWК؋9(xՔӧCs\]տ ܦ  : IV&!^ֳҰ20ߋuZ I  =-!m/R߿øʫ.v pi 9xEKYp2 d܇חVo\  })0453/'z AaȝRǓ(W4-BvEҿ^޼YݸPm'4 $T. 2 .sL QHf;ظV<ϙ0.hX C)04K52P-k%V$Wͅ2YӀ۶GZ $RF20X@g0 v$&@" |f~8  ls /RF(LW'G d`c$Z&M5wT ]fR~j=g&,R FMU { l]%qQi m3j{K566[)/W\'MpfnYFQ".3Lj3 ~.w9iVv` : 0ia_X/Il0Ycz#hZr@dgڑٺM9},^t0 ^h ;YmOb A  1 g.KwC#A N gEYI;X*$:z5,4&KJ._L~'D-6)rtPu'GMSLcKt9}>}" zqz03|U~d Tr|Zߞ{=H0)HۢS\e=poL! &Ip{v_CXݲzަC6N3>;f6^. +l8=4u7K2 ~p aP/(m%ݙ0В>՚`r( &, $xg ~j]yS 23SR{ `%+033k1w,A%1YeԶ5 6ab@ӝǗţ ȿvssF PC\?ץج@ H!  ]L8ޗ߉=8E  (~/476W3*-%\<ݺSaQ ""Xa ?OnrC c6 P ~hA6 h /!e !)w<6.;4T@w inVr f *q}UWu# D- WMJeFa92ZSU}c'mb(J&j%+qY fA2^x^'CNn^"7H6WS Et5OFNOV)z81@E?/Al1K fQ5"!e7b)y=H-tnQsq5-wj8Y3JvYsO3cG"iqAU"gVm_G-Kx:sT?" %~E<5Ls}K]@`9o{F)Bf,IyfG )<+ o`t^Vn a@j. $s~T gC3 <8  NWJI4{ M1[H4<tjG!  v B8 c2w,sa j2x ~  z):16O6< ,XMh Q1JiT r 5 _ D N<fddr**r.  H ' I $U_JZ5BA1A/h.g4fR**F.6XZ%x}O;5Hi8RXYK3 =rcJ7$"!#-29?DINSU[ZZXOI@4. &3>IV[`\UB3vU0 !EijFDiwV& +Tzl4)HYcYQ:& *{B)XaE   -=S^kjlbUI832=R././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685599.0 tryton-7.0.24/tryton/data/sounds/success.wav0000644000175000017500000013530414517761237017250 0ustar00cedcedRIFFWAVEfmt Ddata                     !#),-,,-0 133565 4 ,(""""!#""""      #%#$%--,*%#" #"#+.2-&""'+02:@@==AGNQTSUTQTUTWYXUUROSQMMMNRZccc]YXYQKGGIKOMJIFA?;>BEDGFFIEA@CF"K"H E C F#G)H*L'L$J"J NHGL!S&[-\5[5X5Y6^5a4a4]/\+Z%Y$Y%V#X%Y(T"RH C>988>CBDGILLMMNSY__c\XTNIFB??A?71.)--*'+/2< @EKJHBCJKLP TWYPD < 31.10,(#   %+.465:HVfmkjgb_YSQV[UU_ly  zuyxxrjbTOGA3-%#!&')(%**&"'+)!$""## "() (.5-!    paXWUZ_c ibUPS ]eilpy!z!',-+% ~|    +"4->3I?OMPWMdLcCb@[BVB`HdNkWnZtcrsoruvtuqllpxsi]RXbeZTLNX[`g_WJ>66BFFIB=8/(+/1541{)l%d O>+#"!~zy ~t"v-|11.*,y1t2r,o+k&m"d[X\beb XJ=3,.132((.:AC@{;wkEnFj=k4j1k/s6~9BLOG?97;61y1r210041$!"%   }kc^cjgXVMKG=5+% &% seTBCDD LP!A&2&(';0@>=H9J2D? F NP"O%E&6<*G, !$*& &,.(#(+-4BM|EwFY_joju|yz%5AU[QMYfrm qy%9FMNZv}m`SD681" tsi^bq{jP?CFrDc:U&H 9*#! (0,%!##|!u0n=@?EKPHFEKW`ihejvxy -<OY]ippk[YhuveVOF5) 2KWTG9:sL]jqrhf|\nLgIjRqlqtz 0H b x8Pbejot}#)");M\pxtm`Z1g>\6B$&,>Ohtt nqtxyuhgS&z ~0OwPnCi@bMYTIT!JA62,-.456G @% u^SG7>y3e(_#[%_&n'z { ~zxvsjgkeegrww|zx~!*./"vT=    (,soohizxe]nq`QOM)N0I(Q?`OaQaVpextvq]9%~wd!Y:jKyYgiqvqy(2, /@68Qiv{~$ q`at ~ sgUNPQF:7)! '-CciUD@u:i1QJhh| (59#3H[y,NX[TLJI]kdQC8LZZ YL3W;0+021692vmjvz4JWL<Ukmr~lcf?eid^kr|hH7/y$dN"!5Q[WS`yt}P|Bw:]'KN X nybF38CBHT\di|lxy u  -5('%!"@&e&c&rAgfdu$,8NYPA@Uffqyom[pEd4lLwXRCt'f `|zwbbnxv|dMl2[U ROHT; hQK==4)41 ) &;@OPTCbHvRxA{.*#%.*1!) $2 7&M:RC30%%)/8SnyL)$B805A[l $)?_.jFtTpbl}aThyf`jpaej*0~#y#,x0o1gQ 5++ *;, +6=QE*'9&t ^ ^ap5|]}os~xvm hc)]WX_!I%:0!."34N\8OGE4A&<4?P6XXX_JMEIHRWb`WZP[RO@3*_?6+}d\P8|gQUw(@C5GV|LKRQ3 `OSbiYGBKl~z#e0c=SFP^W_E\,^PVs%&I\m*4y4t&~xbQDA'Q+k3AOVkzznoa^hhm}3A?'!E/]>[9c=rSoncmlQaBJCnTLN]h|{{lD# (PjkkTZL`JvLMMH}8i#T@EPY_L(`E'/-343=]uxr [> *nO)##'+'u^wT}[aQ~INX[Tl 2*790J#SDb'  $/032++./7Nkkv3@H\o >RVdqq\RXd\UOxPmumvs]H9/&phaLJD5 xsOa5H( "o)`i}+9Aey{xc[S[Y[k -E&A*=?DESPTPWGHC.J(LPW_[ZWQDl:SHIWIZ@W,Y<dCpBq*uw v{v!zobkwv|'+ !v g\PNLSU"X2S?\-jq*5CZgaV-n8oKdnsxzb^SFDGEV\w~gab`k y &2?HBBNTeraP>+%@a8nbzy~uxpiPX S"Q&P,F2DE@I(=$tcB26(iFKb]z[uZsNpJu]llZwMCJT4,8DYorkVTTQ@ 4<)2<#KB8 AVD:5.E^W:APOK[~lHA=>0 {w / CQZP@BBHQS]XEYj`K8(xO'2@5_NcVJ599&7.,/(4w.qu~&+v){2BQ_hyqlajQ_JZBJONhbkf~\~[A~.8@Tp~uq!<;*%BSF1%6=HSn6rH[<R>]`_ZUQGx6.jY'y-(#  rrkh_V[~innWrFp]u|p~[rKgLXo\m~msetO[?G*< ' *@LYZcp%k/n2~Kox)6GSOMHb!~ZI&l#vi kt{q^M[eUTO<,oaNE7|1Y<;3@\x u?}p]tGdBok/w?{B:|7lBaJpR|k|m{v $$%,2;?7>6EBIRHYS`WOK>M9_?`;K8D,C;#C-I8S"V 9%0)6F>`;c7e8e?y=>F^zsijwwemx\wMiXn]mgp\[_`YaWaL@@6$$#!5;9BZbbf^\e}#?8@B>IFAWBhMo_if`rcWFMOT\[2 ~ m%e;[-7+)ADY\VUKL^[aTR64$'*" 6Yrqzmy]tRyUR(}tS}VsHlXXR/J1J8`nQGi O G$dm`# y ( i0e  ]t9'E )" V2EucM)d < ! l~(10LRfCa> !y4lNbk    r +/I{+u ,bk 7  i6 J ah$?`0p6'}\#3d[( G \9| F[bf6ܒ RD o\` {D 9s aa W hoLNI}-(:T   `BnB f#  3 h ` H=t[qݵMIg>     TiYR +D h6  1B C v[KE|N r ?v # < -E  eYk6z{ w  >*jw S s L e ޒ^icZ o c;/ W#K)Peݔx J !"Dc c(T n~ a9. |p ,@ n rP$`65z=~ z l5 d3 1 e'\ano05 Z 1 s|#$_ r \A-"݋]+wBj 9 D |]!e -Z3  o 2za tEor;kGf +n= Fg' > a8[Q$ݑ_M; +T @ j =}q eM  l'H5wj g )6 eG)*  ) F _f J 4w = O!RssiO  oT pU~O8 Pi   y|oܢDIk i  %7>ݢ7* n ): E %[ ~  ;{-[/ {Vf Ig  ~X OiZ t =ܿ+f<9 A S l 6@3/R  ]Vs݁G3, Q Y @ ^2$`H  F5X-zBW&!+ _ ( m p'LyQ l xcNr L Vi ~ "c t[9iRhpG & Wf,߲zpx&Z.  M k@ /Z.u  T*ތXiW.{T L 7 ?H*L  -` \ |E{vMg  - \ j \MB%c" Y 3 =`H+(r [ = ,~e ! l >d<ބV[ >  GA` ! ,91X$|jLy; )& *Cd : &VHPX6pqߺ.4= Lh &JFK5uL  Tp@ݲ0@uO 4 O Y{ Y4`   fkt3:} (  w Lp W-`  ?<aT-ުE2^ g Q'd6U  M8bY]ߥ9$ =   *qO2*cI ~ h`S,W S|46 0 zx# ,NB RNE: h K%(ޫ- Ps  I& _  XHqW^݌:{  8}p |wDu US jMrL%ޏY!m l- @ = ^ V  `OaQo4I    6WE xJ; p i^4W\Jp+q@ S \ U'`S_  r 97  VM)>#jgX |8,  k e 0 H3]  V a cAVF<?f iTXj  $ Wr 5._= ( j0Qu(16x w9 N > + ' ]Xp _ zb_d3 q 7O 2 }  F;'` X nY>U8s & i 3Zq 6n { ZEhTK$Z܏q >  /TtW !. 'j m"_9sܫBs0\: Qw Ml ozasc  L^B!t8   M1bF`[ * 3QC3 l V G0r' 4 /JNN܅"SZgM$w-W z{er Q  cB`d9  eS8Tcܿ`b M= s #\o%I ' BRhb|E݈,6-Q (  q #`GI!  t H3U߆6hs N+ P yrZA'5  n{U_3xRgu  t G Co9R-c E nyao:ݱ^ X s  <^5M<L r GY@mu%Z: Q  o , GnEn6, , D T2!>X & y ,G\I7.+ [ %CiC1ppY3pM k V 1 wd^Dxs A VFH)[.SW>~ q  * .:Aio4ua  mo$h5G}Sf =)]O:  dJ381Io(X}  5& NY+mS  @2*J޼ޥ61 _ HK >s N R e( %8޵6Spe    8hyh CZ"#  . q=dF;H[ _ , {\JW <Le K f0 )ܴrsSr  | @  W % ?EGE$k3 Om BG .2 [ Pt;XlL)܆? 8 &S_ Vg"M 2 }FX=[ e  1?eGMJ `' C ! t^1{#I` Y z%*R` ssdcn jK 4q_HuC(ۻ.Y$a #T Rdb)qg m`N  q~!ܭAdxXd ` E # 7 eY Lt)-  eZL3E|5a WP " w^ #d7v[: L %C\Re| Muu i . a sz`33Ry! [9DsܧFJ y r u ` P7Pb(Kb^O >f Y 1 Y  vܻcs[L Dk M*v% -  Ao+@D r u y b  Gn H 0 u kP ;A~f *    gbL+@O* @ p%)7(uG!A 5 % ` {wM 5l QE>Fx۝ Y'[  _ L (%s h@  K&}=mE)P*o e e sP"94T! ",*I6%g9   bP6% {= 5 Tݢn1lY $ ?oe"TI ? 0|Z<k4M   '.GmOpl  Ogޠ<(  } 1 xRmC V t e KPTJk~nd Q Q ZzQMe;u d x 'M^݄ޑM-3 < ~ ppw7W  _2Mn KT  kcz^K i/b-  C'\- c]r > i  .KiWqw Z  rX'J v'~L` #c  =e*qt  h p/6HJ s"$+ M& 9cM~}[`RN6  52 ;: n7 U߬_,KC b ;1! 6 Xj $nC06S5 .Uj @ "  \ vDa UA_P ) l ;% vo   .?q  { & AV?^#mU:p9> U M  D F\ Mq jfeC~@H/6x@r) EB e;X#5~3Y:PP=HNxo'z1 " Lh  (KPeVq[pLGcH\5#G[  ) .tptu%Z2 c]MJ-. X >W>eEpe6>x} 96 #6tybNT &~7OnO%502/V] ]Ji  $e:g}&<;]d3vlVDN =I [y"%z>Uv;qy ]QRt}AR!nI"{SEyMIjTj/E2azZd5?PAwX:= m)c t}^u 9FC^D#^VdL.eu<\ko"Zf 2\VQII-|7J9x2*7#B}oS%M (7W==  7x(F J|TR:CPI?  BN- -#KJ(GyE,N9T8Z^-#J JUgsEZk>A*MM%1PD-]EO\\u|d[> G}?qZL`l9"R/eg'a\1`Z9b].tMo K9MPVcP# FoKZd^nC-^-!$;;~ " Z xPfLz ~U\qQDA!!V=51X1fgjP]sfX0 v4uu>wig5 '72ZIF5|M+F`j\H~HL0Pl\a;?. '2?BT,IOB%vOpS UIG-Y K7!QQGfL]$?Ei\T|ZY}M[ )Zh:$+fOB%N)| kMd q~'MlEz^[siPmL((+"0+"bqn$A:h-P?N*CRlv|~Ru`xZZYt'lKkK2,2D?gawJoecxsmD8+ hi|}ZLQXqe-i}@ Y nhE/C8C ERI0)LmuCO&cAG5|TwESA) WY-/?Q9 aC!<1 c:  ) Pdjin7@Z ^X%{hp hGH|~ -bohIXD"b(;,@*45ejxxtA3|pXXcJn{1bBve#uddKYaEpu?L)W s{d `* B k}Bx<?\s=D"@gSI*`,@0)V.}5DSN`|oEN-n cW-n]E65LN^\2VmF AYiZv {q[4A* x!CP >\=_E3rR+R%&34hG@!MorUCi(kZ9.&y W] d8}9_q.v\go-*> ^M~bOM-B$FgJk+;}GNs 28z"TKb}U\>D0,##p8qI|-.fy@.oUCpCvD8S?G]]$Xdki`(sV]9A5H4f2h?")8>-]$KC_M2  9r:@9#*e119V;6/^?1 mA#z`ZP, !"7XS{, Zo'X%c?:L>0 O?G[cbhQlIQF){j+TN;\*I3o3 ZP[fJs0S614x1U@fHzfVt(ccj `a|p\h,}6RrG${LayU !d+7!UB#-/#zvlflG/Z+d(J 9"~3-5 :NYw)S V|sqaa)mrQ,WZ hxc?~kTPsZyVNs +X[v pR6F) F!3&q%**Fb)YB4-EI6c'8&.6KnVb cU6),  V)*:#Snl$~& sB%?yUYC>X$$:C&QW8GjU.&#% Ejk'@"AP>\%M5/M-p4t m\yP4SN3*z#O o;ba-} wlcB{3|zB(, lF3o0QeSz!Qh)C=~lsXk0yQJ# L\,l:X" "6FZ[>2;nD;t!XS%Cw?BBF-X5DKJuSmjG\ q[cjKW@=\','[ -GG} i*BFI@J H($dJ\hqS)]x~f{9.6t EZIt`YUbY0fJ=--A~ Px@WiU:Bzr\*gG 8~cU;7 * ?XDp2 qsQ 4;z }Vn\Xe>R. ~ag:&\#m-,+*EI8;f q*vWT5 3m$]XqO;*dzdujybR32{vfEo'("aO49VbeeN7?0L[ 1;;8l1xObUm]drDO0$Pz?o?XNU1==9~6t$f#yKLs%6Ad>I:2"Uu[ #qun9lMC'xQ0* G:*},zyI?<8H;=$"xXI_}^whR`oLJq\rkaE5}tOphSA`}KzyN{OFHGgPI2 7qc#bir1 D"yqNtIPHy'_sgSeV d8J,bTfWcSm Y8?I,2ll:ny< [dr@!>^7Z nJOel_Y\W;8-6dw%,J]/+'9DleEK+Sa=X=  j~KRuw>gb2?G&(kBot&i>8i1F+2]tY  n:8g=4}IxLy$La_6jT\T0M3g*YbA!i:'RgQlM"6X)F,%}TWz5y=-'3|;>. )%|0 V\1]1&`\}`)}XUL-AMH%?g/ @n6]l -*-bMzpyPu3r=4)~{aI?gvG0g!hH;'4UoyQ&Z [`oH]LM-|FXO='JQyQ{>T<(&_S'xCSP((%PGvjEs?PcGegi=.K9ttJ! ]XN1(6oAbQ'#'L`lHd  $C1B=h68aVfkqpT a"b@3{SN>Oyh|yjD1%0LB- wz; ]6]@])"O2AYxZKM8$@OP4x +SEvx%gxdyTmK\pqaTVqzw Leo3[-g.2N[f\T[ntOwFZmYlqdPkxhs]v@mq +&xb-?8 $! 88y$BL3mX\pR*fJ}G  @3W8t!k|_u\j@l+J5: LM/^}PaX?s4G].b}W)6Z  S'o0 3><*+:OM@JXTHOKJTYgXbTCbffE"6hzT3/+"T rst>>nrM vQ3#+RC"V0q/Gtoztre0'7+/B!@in^WlyiLP*. xfVL%lFv{sSEq'NOY]^8rvKZXH1[O_neN\}}pkgr?pAZoo#c Q &Fi~7{Id5UNs{_QLKF=C $)(# %Zrl`G7B9%P }focUkSuYi0S>/,(FV U`$jGgXVoRg\SfE0wE"3Tkp|jv 2@9"D"[b n#q*m2Y=+./  @C]uw|`U_aTa37 %2AM>T<[9F64Q$fF $u}smkb^j0@(HB!F&I9l\E9Fd 4 IegY]_&H.5*" (VB{HNRKOt?[Zq{hDt Ekkb]]vqm^UIVEtREK`{ #&jZ:~$M{wY<i'hv l ^V?\V[ekpke^\eoikowcnG`$ac|~t2BO`c\A7}Mae}eyYwDv=8)+7=LVP3 $6.)$) *&=@eX\vTl>h*]\B/.' ,!DFObWnb_mOqAv6<FLVcW0skj~1D9oC!->az W8:&(-024*!%vcTIKJGCT(|BL{?h*YMN< 62'=P\cu} n\VVK5"/Id|/[jxfXP9++017?- -O~rk[WkswDX*<&:9==(2#/$O,mD~\hsz}t^StEq=p0f\ NKRo !;PN8 +-=3M2K(C"C"QZXMFMj Kh|}~mG%w!"<_uijvuqxwk]F1,.|rqleP7($=i}{v|phZQD%skuyrvrjyuyvlo}~pdg{ = Zu{#r1a=T7Q+W&_bfdVC/&%@HA1$sfrajttpbLBYv5HR\iliabhg]Ku@g;a;aIUHS8j4Me}{yoU:;ELIKd7Tblposwvt~silV`8M,8)+% ()(2(@&[2n?r<b4V9X?cF^KW:[.d4c5]%F' "@Z d Q9-% ts   ! !( 0<%G4Y;h6g/h*g g_U K<- !,44C/>+7,>/G0F2K,H(?-A0D,9.#     0CGKNIBG@*"(8EL^gtp c]WXTQ[_b_XW L8&) 4=/    ,244;846)  %.5:@EIKNX]_dhcL-""(27; 8 4 26<?@ 87DMC=76;@NUbe]"b a\ZP@.  ' 178:>; 1 * *,          $((+)'16179+ !",39@8;??>7;97,*(& $,($+% $).*()/53..' ! #'/2/2<9-,-+.69424687475-!#""$$"!!&,/+"      $$#$$%"  !%$%#$# %(*)'"  #!$!!%(+*(#$ ! !                       ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685599.0 tryton-7.0.24/tryton/device_cookie.py0000644000175000017500000000352714517761237016000 0ustar00cedced# This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. import json import logging import os from threading import Lock from tryton.config import CONFIG, get_config_dir logger = logging.getLogger(__name__) COOKIES_PATH = os.path.join(get_config_dir(), 'device_cookies') _lock = Lock() def renew(): from tryton.common import common def set_cookie(new_cookie): try: new_cookie = new_cookie() except Exception: logger.error("Cannot renew device cookie", exc_info=True) else: _set(new_cookie) current_cookie = get() common.RPCExecute( 'model', 'res.user.device', 'renew', current_cookie, process_exception=False, callback=set_cookie) def get(): cookies = _load() return cookies.get(_key()) def _key(): from tryton import common host = CONFIG['login.host'] hostname = common.get_hostname(host) port = common.get_port(host) database = CONFIG['login.db'] username = CONFIG['login.login'] return '%(username)s@%(hostname)s:%(port)s/%(database)s' % { 'username': username, 'hostname': hostname, 'port': port, 'database': database, } def _set(cookie): cookies = _load() cookies[_key()] = cookie try: with _lock: with open(COOKIES_PATH, 'w') as cookies_file: json.dump(cookies, cookies_file) except Exception: logger.error('Unable to save cookies file') def _load(): if not os.path.isfile(COOKIES_PATH): return {} try: with open(COOKIES_PATH) as cookies: cookies = json.load(cookies) except Exception: logger.error("Unable to load device cookies file", exc_info=True) cookies = {} return cookies ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685599.0 tryton-7.0.24/tryton/exceptions.py0000644000175000017500000000054214517761237015363 0ustar00cedced# This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. from .jsonrpc import Fault TrytonServerError = Fault class TrytonServerUnavailable(Exception): pass class TrytonError(Exception): def __init__(self, faultCode): self.faultCode = faultCode ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685599.0 tryton-7.0.24/tryton/fingerprints.py0000644000175000017500000000255414517761237015721 0ustar00cedced# This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. import os from threading import Lock from tryton.config import get_config_dir KNOWN_HOSTS_PATH = os.path.join(get_config_dir(), 'known_hosts') _lock = Lock() def _load(): if not os.path.isfile(KNOWN_HOSTS_PATH): return {} fingerprints = {} with open(KNOWN_HOSTS_PATH) as known_hosts: for line in known_hosts: line = line.strip() try: host, sha1 = line.split(' ') except ValueError: host, sha1 = line, '' fingerprints[host] = sha1 return fingerprints def exists(host): return host in _load() def get(host): return _load().get(host) def set(host, fingerprint): fingerprints = _load() assert isinstance(host, str) if fingerprint: assert len(fingerprint) == 59 # len of formated sha1 else: fingerprint = '' changed = fingerprint != fingerprints.get(host) fingerprints[host] = fingerprint if changed: _save(fingerprints) def _save(fingerprints): with _lock: with open(KNOWN_HOSTS_PATH, 'w') as known_hosts: known_hosts.writelines( '%s %s' % (host, sha1) + os.linesep for host, sha1 in fingerprints.items()) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1745680285.2007778 tryton-7.0.24/tryton/gui/0000755000175000017500000000000015003173635013401 5ustar00cedced././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685599.0 tryton-7.0.24/tryton/gui/__init__.py0000644000175000017500000000027114517761237015524 0ustar00cedced# This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. from .main import Main __all__ = [Main] ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1707150267.0 tryton-7.0.24/tryton/gui/main.py0000644000175000017500000012264714560205673014720 0ustar00cedced# This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. import gettext import json import logging import os import sys import threading import traceback import webbrowser from functools import partial from urllib.parse import parse_qsl, unquote, urlparse from gi.repository import Gdk, GdkPixbuf, Gio, GLib, Gtk import tryton.common as common import tryton.plugins import tryton.rpc as rpc import tryton.translate as translate from tryton.action import Action from tryton.common import RPCContextReload, RPCException, RPCExecute from tryton.common.cellrendererclickablepixbuf import ( CellRendererClickablePixbuf) from tryton.config import CONFIG, TRYTON_ICON, get_config_dir from tryton.exceptions import TrytonError, TrytonServerUnavailable from tryton.gui.window import Window from tryton.jsonrpc import object_hook from tryton.pyson import PYSONDecoder _ = gettext.gettext logger = logging.getLogger(__name__) _PRIORITIES = [getattr(Gio.NotificationPriority, p) for p in ('LOW', 'NORMAL', 'HIGH', 'URGENT')] class Main(Gtk.Application): window = None _instance = None def __new__(cls, *args, **kwargs): if not cls._instance: cls._instance = super().__new__(cls, *args, **kwargs) return cls._instance def do_startup(self): Gtk.Application.do_startup(self) action = Gio.SimpleAction.new('preferences', None) action.connect('activate', lambda *a: self.preferences()) self.add_action(action) self.set_accels_for_action('app.preferences', ['comma']) action = Gio.SimpleAction.new('menu-search', None) action.connect( 'activate', lambda *a: self.global_search_entry.grab_focus()) self.add_action(action) self.set_accels_for_action('app.menu-search', ['k']) action = Gio.SimpleAction.new('menu-toggle', None) action.connect('activate', lambda *a: self.menu_toggle()) self.add_action(action) self.set_accels_for_action('app.menu-toggle', ['m']) toolbar_variant = GLib.Variant.new_string( CONFIG['client.toolbar'] or 'both') action = Gio.SimpleAction.new_stateful( 'toolbar', toolbar_variant.get_type(), toolbar_variant) action.connect('change-state', self.on_change_toolbar) self.add_action(action) def on_change_action_boolean(action, value, key): action.set_state(value) CONFIG[key] = value.get_boolean() if key == 'client.check_version' and CONFIG[key]: common.check_version(self.info) for name, key in [ ('mode-pda', 'client.modepda'), ('save-tree-width', 'client.save_tree_width'), ('save-tree-state', 'client.save_tree_state'), ('code-scanner-sound', 'client.code_scanner_sound'), ('spell-checking', 'client.spellcheck'), ('check-version', 'client.check_version'), ]: variant = GLib.Variant.new_boolean(CONFIG[key]) action = Gio.SimpleAction.new_stateful(name, None, variant) action.connect('change-state', on_change_action_boolean, key) self.add_action(action) action = Gio.SimpleAction.new('tab-previous', None) action.connect('activate', lambda *a: self.win_prev()) self.add_action(action) self.set_accels_for_action('app.tab-previous', ['Tab']) action = Gio.SimpleAction.new('tab-next', None) action.connect('activate', lambda *a: self.win_next()) self.add_action(action) self.set_accels_for_action('app.tab-next', ['Tab']) action = Gio.SimpleAction.new('search-limit', None) action.connect('activate', lambda *a: self.edit_limit()) self.add_action(action) action = Gio.SimpleAction.new('documentation', None) action.connect('activate', lambda *a: common.open_documentation()) self.add_action(action) self._shortcuts = None action = Gio.SimpleAction.new('shortcuts', None) action.connect('activate', lambda *a: self.shortcuts()) self.add_action(action) self.set_accels_for_action('app.shortcuts', ['F1']) action = Gio.SimpleAction.new('about', None) action.connect('activate', lambda *a: self.about()) self.add_action(action) action = Gio.SimpleAction.new('quit', None) action.connect('activate', self.on_quit) self.add_action(action) self.set_accels_for_action('app.quit', ['q']) def do_activate(self): if self.window: self.window.present() return self.window = Gtk.ApplicationWindow(application=self, title="Tryton") self.window.set_default_size(1280, 960) self.window.maximize() self.window.set_position(Gtk.WindowPosition.CENTER) self.window.set_resizable(True) self.window.set_icon(TRYTON_ICON) self.window.connect("destroy", self.on_quit) self.window.connect("delete_event", self.on_quit) common.setup_window(self.window) self.header = Gtk.HeaderBar.new() self.header.set_show_close_button(True) self.window.set_titlebar(self.header) self.set_title() self.primary_menu = Gtk.MenuButton.new() self.primary_menu.set_menu_model(self._get_primary_menu()) self.header.pack_end(self.primary_menu) menu = Gtk.Button.new() menu.set_relief(Gtk.ReliefStyle.NONE) menu.set_image( common.IconFactory.get_image('tryton-menu', Gtk.IconSize.BUTTON)) menu.connect('clicked', self.menu_toggle) self.header.pack_start(menu) favorite = Gtk.MenuButton.new() favorite.set_relief(Gtk.ReliefStyle.NONE) favorite.set_image(common.IconFactory.get_image( 'tryton-bookmarks', Gtk.IconSize.BUTTON)) self.menu_favorite = Gtk.Menu.new() favorite.set_popup(self.menu_favorite) favorite.connect('button-press-event', self.favorite_set) self.header.pack_start(favorite) self.set_global_search() self.header.pack_start(self.global_search_entry) self.accel_group = Gtk.AccelGroup() self.window.add_accel_group(self.accel_group) Gtk.AccelMap.add_entry( '/Form/New', Gdk.KEY_N, Gdk.ModifierType.CONTROL_MASK) Gtk.AccelMap.add_entry( '/Form/Save', Gdk.KEY_S, Gdk.ModifierType.CONTROL_MASK) Gtk.AccelMap.add_entry( '/Form/Duplicate', Gdk.KEY_D, Gdk.ModifierType.CONTROL_MASK | Gdk.ModifierType.SHIFT_MASK) Gtk.AccelMap.add_entry( '/Form/Delete', Gdk.KEY_D, Gdk.ModifierType.CONTROL_MASK) Gtk.AccelMap.add_entry( '/Form/Next', Gdk.KEY_Down, Gdk.ModifierType.CONTROL_MASK) Gtk.AccelMap.add_entry( '/Form/Previous', Gdk.KEY_Up, Gdk.ModifierType.CONTROL_MASK) Gtk.AccelMap.add_entry( '/Form/Switch View', Gdk.KEY_L, Gdk.ModifierType.CONTROL_MASK) Gtk.AccelMap.add_entry( '/Form/Close', Gdk.KEY_W, Gdk.ModifierType.CONTROL_MASK) Gtk.AccelMap.add_entry( '/Form/Reload', Gdk.KEY_R, Gdk.ModifierType.CONTROL_MASK) Gtk.AccelMap.add_entry( '/Form/Attachments', Gdk.KEY_T, Gdk.ModifierType.CONTROL_MASK | Gdk.ModifierType.SHIFT_MASK) Gtk.AccelMap.add_entry( '/Form/Notes', Gdk.KEY_O, Gdk.ModifierType.CONTROL_MASK | Gdk.ModifierType.SHIFT_MASK) Gtk.AccelMap.add_entry( '/Form/Relate', Gdk.KEY_R, Gdk.ModifierType.CONTROL_MASK | Gdk.ModifierType.SHIFT_MASK) Gtk.AccelMap.add_entry( '/Form/Actions', Gdk.KEY_E, Gdk.ModifierType.CONTROL_MASK) Gtk.AccelMap.add_entry( '/Form/Report', Gdk.KEY_P, Gdk.ModifierType.CONTROL_MASK) Gtk.AccelMap.add_entry( '/Form/Email', Gdk.KEY_E, Gdk.ModifierType.CONTROL_MASK | Gdk.ModifierType.SHIFT_MASK) Gtk.AccelMap.add_entry( '/Form/Search', Gdk.KEY_F, Gdk.ModifierType.CONTROL_MASK | Gdk.ModifierType.SHIFT_MASK) Gtk.AccelMap.load(os.path.join(get_config_dir(), 'accel.map')) self.tooltips = common.Tooltips() self.vbox = Gtk.VBox() self.window.add(self.vbox) self.buttons = {} self.info = Gtk.VBox() self.vbox.pack_start(self.info, expand=False, fill=True, padding=0) if CONFIG['client.check_version']: common.check_version(self.info) GLib.timeout_add_seconds( int(CONFIG['download.frequency']), common.check_version, self.info) self.pane = Gtk.HPaned() self.vbox.pack_start(self.pane, expand=True, fill=True, padding=0) self.pane.set_position(int(CONFIG['menu.pane'])) self.menu_screen = None self.menu = Gtk.VBox() self.menu.set_vexpand(True) self.pane.add1(self.menu) self.notebook = Gtk.Notebook() self.notebook.popup_enable() self.notebook.set_scrollable(True) self.notebook.connect_after('switch-page', self._sig_page_changt) self.pane.add2(self.notebook) self.window.show_all() self.pages = [] self.previous_pages = {} self.current_page = 0 self.last_page = 0 self.dialogs = [] # Register plugins tryton.plugins.register() self.set_title() # Adds username/profile while password is asked try: common.get_credentials() except Exception as exception: if not isinstance(exception, TrytonError): common.error(exception, traceback.format_exc()) elif exception.faultCode == 'SessionFailed': common.message( _("Could not get a session."), msg_type=Gtk.MessageType.ERROR) return self.quit() self.get_preferences() def do_command_line(self, cmd): self.do_activate() arguments = cmd.get_arguments() if len(arguments) > 1: url = arguments[1] self.open_url(url) return True def do_shutdown(self): Gtk.Application.do_shutdown(self) common.Logout() CONFIG.save() Gtk.AccelMap.save(os.path.join(get_config_dir(), 'accel.map')) def on_quit(self, *args): try: if not self.close_pages(): return True except TrytonServerUnavailable: pass rpc.logout() self.quit() def add_window(self, window): super().add_window(window) common.setup_window(window) def _get_primary_menu(self): menu = Gio.Menu.new() menu.append(_("Preferences..."), 'app.preferences') section = Gio.Menu.new() toolbar = Gio.Menu.new() section.append_submenu(_("Toolbar"), toolbar) toolbar.append(_("Default"), 'app.toolbar::default') toolbar.append(_("Text and Icons"), 'app.toolbar::both') toolbar.append(_("Text"), 'app.toolbar::text') toolbar.append(_("Icons"), 'app.toolbar::icons') form = Gio.Menu.new() section.append_submenu(_("Form"), form) form.append(_("Save Column Width"), 'app.save-tree-width') form.append(_("Save Tree State"), 'app.save-tree-state') form.append(_("Spell Checking"), 'app.spell-checking') form.append(_("Play Sound for Code Scanner"), 'app.code-scanner-sound') section.append(_("PDA Mode"), 'app.mode-pda') section.append(_("Search Limit..."), 'app.search-limit') section.append(_("Check Version"), 'app.check-version') menu.append_section(_("Options"), section) section = Gio.Menu.new() section.append(_("Documentation..."), 'app.documentation') section.append(_("Keyboard Shortcuts..."), 'app.shortcuts') section.append(_("About..."), 'app.about') menu.append_section(_("Help"), section) return menu def set_global_search(self): self.global_search_entry = Gtk.Entry.new() self.global_search_entry.set_width_chars(20) global_search_completion = Gtk.EntryCompletion() global_search_completion.set_match_func(lambda *a: True) global_search_completion.set_model( Gtk.ListStore(GdkPixbuf.Pixbuf, str, str, int, str)) pixbuf_cell = Gtk.CellRendererPixbuf() global_search_completion.pack_start(pixbuf_cell, expand=True) global_search_completion.add_attribute(pixbuf_cell, 'pixbuf', 0) text_cell = Gtk.CellRendererText() global_search_completion.pack_start(text_cell, expand=True) global_search_completion.add_attribute(text_cell, "markup", 1) global_search_completion.props.popup_set_width = True self.global_search_entry.set_completion(global_search_completion) def match_selected(completion, model, iter_): model, record_id, model_name = model.get(iter_, 2, 3, 4) if model == self.menu_screen.model_name: # ids is not defined to prevent to add suffix Action.exec_keyword('tree_open', { 'model': model, 'id': record_id, }) else: Window.create(model, res_id=record_id, mode=['form', 'tree'], name=model_name) self.global_search_entry.set_text('') return True global_search_completion.connect('match-selected', match_selected) def update(widget, search_text, callback=None): def end(): if callback: callback() return False if search_text != widget.get_text(): return end() gmodel = global_search_completion.get_model() if not search_text or not gmodel: gmodel.clear() gmodel.search_text = search_text return end() if getattr(gmodel, 'search_text', None) == search_text: return end() def set_result(result): try: result = result() except RPCException: result = [] if search_text != widget.get_text(): if callback: callback() return False gmodel.clear() for r in result: _, model, model_name, record_id, record_name, icon = r if icon: text = common.to_xml(record_name) pixbuf = common.IconFactory.get_pixbuf( icon, Gtk.IconSize.BUTTON) else: text = '%s:\n %s' % ( common.to_xml(model_name), common.to_xml(record_name)) pixbuf = None gmodel.append([pixbuf, text, model, record_id, model_name]) gmodel.search_text = search_text # Force display of popup widget.emit('changed') end() RPCExecute('model', 'ir.model', 'global_search', search_text, CONFIG['client.limit'], self.menu_screen.model_name, context=self.menu_screen.context, callback=set_result) return False def changed(widget): search_text = widget.get_text() GLib.timeout_add(300, update, widget, search_text) def activate(widget): def message(): gmodel = global_search_completion.get_model() if not len(gmodel): common.message(_('No result found.')) else: widget.emit('changed') search_text = widget.get_text() update(widget, search_text, message) self.global_search_entry.connect('changed', changed) self.global_search_entry.connect('activate', activate) def set_title(self, value=''): if CONFIG['login.profile']: login_info = CONFIG['login.profile'] else: login_info = '%s@%s/%s' % ( CONFIG['login.login'], CONFIG['login.host'], CONFIG['login.db']) titles = [] if value: titles.append(value) titles.append(CONFIG['client.title']) self.header.set_title(' - '.join(titles)) self.header.set_subtitle(login_info) try: style_context = self.header.get_style_context() except AttributeError: pass else: for name in style_context.list_classes(): if name.startswith('profile-'): style_context.remove_class(name) if CONFIG['login.profile']: style_context.add_class( 'profile-%s' % CONFIG['login.profile']) def favorite_set(self, *args): if self.menu_favorite.get_children(): return def _action_favorite(widget, id_): event = Gtk.get_current_event() allow_similar = (event.state & Gdk.ModifierType.MOD1_MASK or event.state & Gdk.ModifierType.SHIFT_MASK) with Window(allow_similar=allow_similar): # ids is not defined to prevent to add suffix Action.exec_keyword('tree_open', { 'model': self.menu_screen.model_name, 'id': id_, }) def _manage_favorites(widget): Window.create(self.menu_screen.model_name + '.favorite', mode=['tree', 'form'], name=_("Favorites")) try: favorites = RPCExecute('model', self.menu_screen.model_name + '.favorite', 'get', process_exception=False) except Exception: return False for id_, name, icon in favorites: menuitem = Gtk.MenuItem(label=name) menuitem.connect('activate', _action_favorite, id_) self.menu_favorite.add(menuitem) self.menu_favorite.add(Gtk.SeparatorMenuItem()) manage_favorites = Gtk.MenuItem(label=_("Manage..."), use_underline=True) manage_favorites.connect('activate', _manage_favorites) self.menu_favorite.add(manage_favorites) self.menu_favorite.show_all() def favorite_unset(self): for child in self.menu_favorite.get_children(): self.menu_favorite.remove(child) def on_change_toolbar(self, action, value): action.set_state(value) option = value.get_string() CONFIG['client.toolbar'] = option if option == 'default': barstyle = False elif option == 'both': barstyle = Gtk.ToolbarStyle.BOTH elif option == 'text': barstyle = Gtk.ToolbarStyle.TEXT elif option == 'icons': barstyle = Gtk.ToolbarStyle.ICONS for page_idx in range(self.notebook.get_n_pages()): page = self.get_page(page_idx) if page.toolbar: page.toolbar.set_style(barstyle) def edit_limit(self): from tryton.gui.window.limit import Limit Limit().run() def win_next(self): page = self.notebook.get_current_page() if page == len(self.pages) - 1: page = -1 self.notebook.set_current_page(page + 1) def win_prev(self): page = self.notebook.get_current_page() self.notebook.set_current_page(page - 1) def get_preferences(self): RPCContextReload() try: prefs = RPCExecute('model', 'res.user', 'get_preferences', False) except RPCException: prefs = {} targets = [ common.IconFactory.load_icons, common.MODELACCESS.load_models, common.MODELHISTORY.load_history, common.MODELNOTIFICATION.load_names, common.VIEW_SEARCH.load_searches, ] if CONFIG['thread']: threads = [] for target in targets: t = threading.Thread(target=target) threads.append(t) t.start() for t in threads: t.join() else: for target in targets: target() if prefs and 'language_direction' in prefs: translate.set_language_direction(prefs['language_direction']) CONFIG['client.language_direction'] = \ prefs['language_direction'] self.sig_win_menu(prefs=prefs) for action_id in prefs.get('actions', []): Action.execute(action_id, {}) self.set_title(prefs.get('status_bar', '')) if prefs and 'language' in prefs: translate.setlang(prefs['language'], prefs.get('locale')) if CONFIG['client.lang'] != prefs['language']: self.favorite_unset() self.primary_menu.set_menu_model(self._get_primary_menu()) CONFIG['client.lang'] = prefs['language'] common.MODELNAME.clear() # Set placeholder after language is set to get correct translation self.global_search_entry.set_placeholder_text(_("Action")) CONFIG.save() def preferences(self): from tryton.gui.window.preference import Preference if not self.close_pages(): return False Preference(rpc._USER, self.get_preferences) def sig_win_close(self, widget=None): self._sig_remove_book(widget, self.notebook.get_nth_page(self.notebook.get_current_page())) def close_pages(self): if self.notebook.get_n_pages(): if not common.sur( _('The following action requires to close all tabs.\n' 'Do you want to continue?')): return False res = True while res: wid = self.get_page() if wid: if not wid.sig_close(): return False res = self._win_del() else: res = False if self.menu_screen: self.menu_screen.save_tree_state() if self.menu.get_visible(): CONFIG['menu.pane'] = self.pane.get_position() return True def about(self): from tryton.gui.window.about import About About() def shortcuts(self): if self._shortcuts: self._shortcuts.destroy() self._shortcuts = window = Gtk.ShortcutsWindow() window.set_transient_for(common.get_toplevel_window()) window.set_position(Gtk.WindowPosition.CENTER_ALWAYS) window.set_destroy_with_parent(True) self.add_window(window) section = Gtk.ShortcutsSection() section.props.section_name = 'app' section.props.title = _("Application Shortcuts") section.props.visible = True group = Gtk.ShortcutsGroup() group.props.title = _("Global") section.add(group) for action, title in [ ('app.preferences', _("Preferences")), ('app.menu-search', _("Search menu")), ('app.menu-toggle', _("Toggle menu")), ('app.tab-previous', _("Previous tab"), ), ('app.tab-next', _("Next tab")), ('app.shortcuts', _("Shortcuts")), ('app.quit', _("Quit")), ]: shortcut = Gtk.ShortcutsShortcut() shortcut.props.title = title accels = self.get_accels_for_action(action) if accels: shortcut.props.accelerator = ' '.join(accels) group.add(shortcut) window.add(section) for name, title, groups in [ ('entry', _("Edition Shortcuts"), [ (_("Text Entries"), [ ('x', _("Cut selected text")), ('c', _("Copy selected text")), ('v', _("Paste copied text")), ('Tab', _("Next entry")), ('Tab', _("Previous entry")), ]), (_("Relation Entries"), [ ('F3', _("Create new relation")), ('F2', _("Open/Search relation")), ]), (_("List Entries"), [ ('F4', _("Switch view")), ('F3', _("Create/Select new line")), ('F2', _("Open relation")), ('Delete', _("Mark line for deletion/removal")), ('Delete', _("Mark line for removal")), ('Insert', _("Unmark line for deletion")), ]), ]), ('tree', _("List/Tree Shortcuts"), [ (_("Move Cursor"), [ ('Right', _("Move right")), ('Left', _("Move left")), ('Up', _("Move up")), ('Down', _("Move down")), ('Page_Up', _("Move up of one page")), ('Page_Down', _("Move down of one page")), ('Home', _("Move to top")), ('End', _("Move to bottom")), ('BackSpace', _("Move to parent")), ]), (_("Edition"), [ ('c', _("Copy selected rows")), ('v', _("Insert copied rows")), ]), (_("Selection"), [ ('a slash', _("Select all")), ('a slash', _("Unselect all")), ('BackSpace', _("Select parent")), ('space', _("Select/Activate current row")), ('space Return', _("Select/Activate current row")), ('space', _("Toggle selection")), ]), (_("Expand/Collapse"), [ ('plus', _("Expand row")), ('minus', _("Collapse row")), ('space', _("Toggle row")), ('Left', _("Collapse all rows")), ('Right', _("Expand all rows")), ]), ]), ]: section = Gtk.ShortcutsSection() section.props.section_name = name section.props.title = title section.props.visible = True for title, shortcuts in groups: group = Gtk.ShortcutsGroup() group.props.title = title section.add(group) for accelerator, title in shortcuts: shortcut = Gtk.ShortcutsShortcut() shortcut.props.title = title shortcut.props.accelerator = accelerator group.add(shortcut) window.add(section) window.show_all() def menu_toggle(self, *args): if self.menu.get_visible(): CONFIG['menu.pane'] = self.pane.get_position() self.pane.set_position(0) self.notebook.grab_focus() self.menu.set_visible(False) else: self.pane.set_position(int(CONFIG['menu.pane'])) self.menu.set_visible(True) if self.menu_screen: self.menu_screen.set_cursor() def menu_row_activate(self): screen = self.menu_screen record_id = (screen.current_record.id if screen.current_record else None) # ids is not defined to prevent to add suffix return Action.exec_keyword('tree_open', { 'model': screen.model_name, 'id': record_id, }, warning=False) def sig_win_menu(self, prefs=None): from tryton.gui.window.view_form.screen import Screen if not prefs: try: prefs = RPCExecute('model', 'res.user', 'get_preferences', False) except RPCException: return False if self.menu_screen: self.menu_screen.save_tree_state() for child in self.menu.get_children(): self.menu.remove(child) action = PYSONDecoder().decode(prefs['pyson_menu']) view_ids = [] if action.get('views', []): view_ids = [x[0] for x in action['views']] elif action.get('view_id', False): view_ids = [action['view_id'][0]] ctx = rpc.CONTEXT.copy() decoder = PYSONDecoder(ctx) action_ctx = decoder.decode(action.get('pyson_context') or '{}') domain = decoder.decode(action['pyson_domain']) screen = Screen(action['res_model'], mode=['tree'], view_ids=view_ids, domain=domain, context=action_ctx, readonly=True, limit=None, row_activate=self.menu_row_activate) # Use alternate view to not show search box screen.screen_container.alternate_view = True screen.switch_view(view_type=screen.current_view.view_type) self.menu.pack_start( screen.screen_container.alternate_viewport, expand=True, fill=True, padding=0) treeview = screen.current_view.treeview treeview.set_headers_visible(False) # Favorite column column = Gtk.TreeViewColumn() column.name = None column._type = None favorite_renderer = CellRendererClickablePixbuf() column.pack_start(favorite_renderer, expand=False) def favorite_setter(column, cell, store, iter_, user_data=None): menu = store.get_value(iter_, 0) favorite = menu.value.get('favorite') if favorite: icon = 'tryton-star' elif favorite is False: icon = 'tryton-star-border' else: icon = None if icon: pixbuf = common.IconFactory.get_pixbuf( icon, Gtk.IconSize.MENU) else: pixbuf = None cell.set_property('pixbuf', pixbuf) column.set_cell_data_func(favorite_renderer, favorite_setter) def toggle_favorite(renderer, path, treeview): if treeview.props.window: self.toggle_favorite(renderer, path, treeview) favorite_renderer.connect('clicked', lambda *a: GLib.idle_add(toggle_favorite, *a), treeview) # Unset fixed height mode to add column treeview.set_fixed_height_mode(False) treeview.set_property( 'enable-grid-lines', Gtk.TreeViewGridLines.NONE) treeview.append_column(column) screen.search_filter() screen.display(set_cursor=True) self.menu_screen = screen def toggle_favorite(self, renderer, path, treeview): store = treeview.get_model() iter_ = store.get_iter(path) menu = store.get_value(iter_, 0) favorite = menu.value.get('favorite') if favorite: value = False method = 'unset' elif favorite is False: value = True method = 'set' else: return try: RPCExecute('model', self.menu_screen.model_name + '.favorite', method, menu.id) except RPCException: return menu.value['favorite'] = value store.row_changed(path, iter_) self.favorite_unset() def win_set(self, page): current_page = self.notebook.get_current_page() page_num = self.notebook.page_num(page.widget) page.widget.props.visible = True self.notebook.set_current_page(page_num) # In order to focus the page if current_page == page_num: self._sig_page_changt(self.notebook, None, page_num) def win_add(self, page, hide_current=False): previous_page_id = self.notebook.get_current_page() previous_widget = self.notebook.get_nth_page(previous_page_id) if previous_widget and hide_current: page_id = previous_page_id + 1 else: page_id = -1 self.previous_pages[page] = previous_widget self.pages.append(page) hbox = Gtk.HBox(spacing=3) if page.icon: hbox.pack_start( common.IconFactory.get_image( page.icon, Gtk.IconSize.SMALL_TOOLBAR), expand=False, fill=False, padding=0) name = page.name label = Gtk.Label( label=common.ellipsize(name.split(' / ')[-1], 20), halign=Gtk.Align.START) self.tooltips.set_tip(label, page.name) self.tooltips.enable() hbox.pack_start(label, expand=True, fill=True, padding=0) button = Gtk.Button() button.set_relief(Gtk.ReliefStyle.NONE) button.set_can_focus(False) button.add(common.IconFactory.get_image( 'tryton-close', Gtk.IconSize.MENU)) self.tooltips.set_tip(button, _('Close Tab')) button.connect('clicked', self._sig_remove_book, page.widget) hbox.pack_start(button, expand=False, fill=False, padding=0) size = Gtk.IconSize.lookup(Gtk.IconSize.MENU) button.set_size_request(size.width, size.height) hbox.show_all() label_menu = Gtk.Label( label=page.name, halign=Gtk.Align.START) page.widget.props.margin = 3 self.notebook.insert_page_menu(page.widget, hbox, label_menu, page_id) self.notebook.set_tab_reorderable(page.widget, True) self.notebook.set_current_page(page_id) def _sig_remove_book(self, widget, page_widget): for page in self.pages: if page.widget == page_widget: if not page.widget.props.sensitive: return page_num = self.notebook.page_num(page.widget) self.notebook.set_current_page(page_num) res = page.sig_close() if not res: return self._win_del(page_widget) def _win_del(self, page_widget=None): if page_widget: page_id = self.notebook.page_num(page_widget) else: page_id = int(self.notebook.get_current_page()) page_widget = self.notebook.get_nth_page(page_id) if page_id != -1: page = None for i in range(len(self.pages)): if self.pages[i].widget == page_widget: page = self.pages.pop(i) break self.notebook.remove_page(page_id) next_page_id = -1 to_pop = [] for i in self.previous_pages: if self.previous_pages[i] == page_widget: to_pop.append(i) if i.widget == page_widget: if self.previous_pages[i]: next_page_id = self.notebook.page_num( self.previous_pages[i]) to_pop.append(i) to_pop.reverse() for i in to_pop: self.previous_pages.pop(i) if hasattr(page, 'destroy'): page.destroy() del page current_widget = self.notebook.get_nth_page(next_page_id) if current_widget: current_widget.props.visible = True self.notebook.set_current_page(next_page_id) if not self.pages and self.menu_screen: self.menu_screen.set_cursor() return self.notebook.get_current_page() != -1 def get_page(self, page_id=None): if page_id is None: page_id = self.notebook.get_current_page() if page_id == -1: return None page_widget = self.notebook.get_nth_page(page_id) for page in self.pages: if page.widget == page_widget: return page return None def _sig_page_changt(self, notebook, page, page_num): self.last_page = self.current_page last_form = self.get_page(self.current_page) if last_form: for dialog in last_form.dialogs: dialog.hide() self.current_page = self.notebook.get_current_page() current_form = self.get_page(self.current_page) def set_cursor(): if self.current_page == self.notebook.get_current_page(): current_form.set_cursor() # Using idle_add because the Gtk.TreeView grabs the focus at the # end of the event GLib.idle_add(set_cursor) for dialog in current_form.dialogs: dialog.show() def _open_url(self, url): loads = partial(json.loads, object_hook=object_hook) urlp = urlparse(url) if not urlp.scheme == 'tryton': return urlp = urlparse('http' + url[6:]) database, path = list(map(unquote, (urlp.path[1:].split('/', 1) + [''])[:2])) if not path: return type_, path = (path.split('/', 1) + [''])[:2] params = {} if urlp.params: try: params.update(dict(parse_qsl(urlp.params, strict_parsing=True))) except ValueError: return def open_model(path): attributes = {} model, path = (path.split('/', 1) + [''])[:2] if not model: return attributes = {} try: attributes['view_ids'] = loads(params.get('views', '[]')) if 'limit' in params: attributes['limit'] = loads(params.get('limit', 'null')) attributes['name'] = loads(params.get('name', '""')) attributes['search_value'] = loads( params.get('search_value', '[]')) attributes['domain'] = loads(params.get('domain', '[]')) attributes['context'] = loads(params.get('context', '{}')) attributes['context_model'] = params.get('context_model') attributes['tab_domain'] = loads( params.get('tab_domain', '[]')) except ValueError: return if path: try: attributes['res_id'] = int(path) except ValueError: return attributes['mode'] = ['form', 'tree'] try: Window.create(model, **attributes) except Exception: # Prevent crashing the client return def open_wizard(wizard): attributes = {} if not wizard: return try: attributes['data'] = loads(params.get('data', '{}')) attributes['direct_print'] = loads( params.get('direct_print', 'false')) attributes['name'] = loads(params.get('name', '""')) attributes['window'] = loads(params.get('window', 'false')) attributes['context'] = loads(params.get('context', '{}')) except ValueError: return try: Window.create_wizard(wizard, **attributes) except Exception: # Prevent crashing the client return def open_report(report): attributes = {} if not report: return try: attributes['data'] = loads(params.get('data', '{}')) attributes['direct_print'] = loads( params.get('direct_print', 'false')) attributes['context'] = loads(params.get('context', '{}')) except ValueError: return try: Action.exec_report(report, **attributes) except Exception: # Prevent crashing the client return def open_url(): try: url = loads(params.get('url', 'false')) except ValueError: return if url: webbrowser.open(url, new=2) if type_ == 'model': open_model(path) elif type_ == 'wizard': open_wizard(path) elif type_ == 'report': open_report(path) elif type_ == 'url': open_url() self.window.present() def open_url(self, url): def idle_open_url(): self._open_url(url) return False GLib.idle_add(idle_open_url) def show_notification(self, title, msg, priority=1): notification = Gio.Notification.new(title) notification.set_body(msg) notification.set_priority(_PRIORITIES[priority]) if sys.platform != 'win32' or GLib.glib_version >= (2, 57, 0): self.send_notification(None, notification) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1745680285.2074447 tryton-7.0.24/tryton/gui/window/0000755000175000017500000000000015003173635014710 5ustar00cedced././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685599.0 tryton-7.0.24/tryton/gui/window/__init__.py0000644000175000017500000000027714517761237017041 0ustar00cedced# This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. from .window import Window __all__ = [Window] ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1745680282.0 tryton-7.0.24/tryton/gui/window/about.py0000644000175000017500000000327615003173632016401 0ustar00cedced# -*- coding: utf-8 -*- # This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. import gettext import os from gi.repository import GdkPixbuf, Gtk from tryton import __version__ from tryton.common import get_toplevel_window from tryton.config import CONFIG, PIXMAPS_DIR COPYRIGHT = '''\ Copyright (C) 2004-2025 Tryton. ''' AUTHORS = [ 'Bertrand Chenal ', 'Cédric Krier ', 'Franz Wiesinger', 'Hartmut Goebel', 'Korbinian Preisler ', 'Mathias Behrle ', 'Maxime Richez ', 'Nicolas Évrard ', 'Sednacom ', 'Udo Spallek ', ] _ = gettext.gettext class About(object): def __init__(self): parent = get_toplevel_window() self.win = Gtk.AboutDialog() self.win.set_transient_for(parent) self.win.set_name(CONFIG['client.title']) self.win.set_version(__version__) self.win.set_comments(_("modularity, scalability and security")) self.win.set_copyright(COPYRIGHT) self.win.set_license_type(Gtk.License.GPL_3_0) self.win.set_website('http://www.tryton.org/') self.win.set_website_label("Tryton") self.win.set_authors(AUTHORS) self.win.set_translator_credits(_('translator-credits')) self.win.set_logo(GdkPixbuf.Pixbuf.new_from_file( os.path.join(PIXMAPS_DIR, 'tryton.svg'))) self.win.run() parent.present() self.win.destroy() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685599.0 tryton-7.0.24/tryton/gui/window/attachment.py0000644000175000017500000000715514517761237017434 0ustar00cedced# This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. "Attachment" import gettext import os import webbrowser from functools import partial from urllib.parse import unquote, urlparse from tryton.common import ( RPCException, RPCExecute, file_open, file_write, url_open) from tryton.gui.window.view_form.screen import Screen from tryton.gui.window.win_form import WinForm _ = gettext.gettext class Attachment(WinForm): "Attachment window" def __init__(self, record, callback=None): self.resource = '%s,%s' % (record.model_name, record.id) self.attachment_callback = callback title = _('Attachments (%s)') % (record.rec_name()) screen = Screen('ir.attachment', domain=[ ('resource', '=', self.resource), ], mode=['tree', 'form']) super(Attachment, self).__init__(screen, self.callback, view_type='tree', title=title) screen.search_filter() def destroy(self): self.prev_view.save_width() super(Attachment, self).destroy() def callback(self, result): if result: self.screen.save_current() if self.attachment_callback: self.attachment_callback() def add_uri(self, uri): data_field = self.screen.group.fields['data'] link_field = self.screen.group.fields['link'] type_field = self.screen.group.fields['type'] name_field = self.screen.group.fields[data_field.attrs['filename']] description_field = self.screen.group.fields['description'] new_record = self.screen.new() parse = urlparse(uri) if not parse.scheme: description_field.set_client(new_record, uri) else: file_name = os.path.basename(unquote(parse.path)) name_field.set_client(new_record, file_name) if parse.scheme == 'file': data_field.set_client(new_record, url_open(uri).read()) type_field.set_client(new_record, 'data') else: link_field.set_client(new_record, uri) type_field.set_client(new_record, 'link') self.screen.display() def add_file(self, filename): self.add_uri(filename.as_uri()) @staticmethod def get_attachments(record): attachments = [] context = {} if record and record.id >= 0: context = record.get_context() try: attachments = RPCExecute('model', 'ir.attachment', 'search_read', [ ('resource', '=', '%s,%s' % ( record.model_name, record.id)), ], 0, 20, None, ['rec_name', 'name', 'type', 'link'], context=context) except RPCException: pass for attachment in attachments: name = attachment['rec_name'] callback = getattr( Attachment, 'open_' + attachment['type'], Attachment.open_data) yield name, partial( callback, attachment=attachment, context=context) @staticmethod def open_link(attachment, context): if attachment['link']: webbrowser.open(attachment['link'], new=2) @staticmethod def open_data(attachment, context): try: value, = RPCExecute('model', 'ir.attachment', 'read', [attachment['id']], ['data'], context=context) except RPCException: return filepath = file_write(attachment['name'], value['data']) file_open(filepath) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685599.0 tryton-7.0.24/tryton/gui/window/board.py0000644000175000017500000000355714517761237016375 0ustar00cedced# This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. "Board" import gettext import xml.dom.minidom from tryton.common import MODELNAME, RPCExecute from tryton.gui import Main from tryton.gui.window.view_board import ViewBoard from .tabcontent import TabContent _ = gettext.gettext class Board(TabContent): 'Board' def __init__(self, model, name='', **attributes): super(Board, self).__init__(**attributes) context = attributes.get('context') self.view_id, = attributes.get('view_ids') view = RPCExecute( 'model', 'ir.ui.view', 'view_get', self.view_id, context=context) xml_dom = xml.dom.minidom.parseString(view['arch']) root, = xml_dom.childNodes self.board = ViewBoard(root, context=context) self.model = model self.dialogs = [] if not name: name = MODELNAME.get(model) self.name = name self.create_tabcontent() self.board.reload() def get_toolbars(self): return {} def widget_get(self): return self.board.widget_get() def sig_reload(self, test_modified=True): self.board.reload() return True def sig_close(self): return True def compare(self, model, attributes): if not attributes: return False return (self.model == model and self.attributes.get('view_ids') == attributes.get('view_ids') and self.attributes.get('context') == attributes.get('context')) def __hash__(self): return id(self) def sig_win_close(self, widget): Main().sig_win_close(widget) def set_cursor(self): if not self.board.actions: return first_action = self.board.actions[0] first_action.screen.set_cursor() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685599.0 tryton-7.0.24/tryton/gui/window/code_scanner.py0000644000175000017500000000664714517761237017734 0ustar00cedced# This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. import gettext try: from http import HTTPStatus except ImportError: from http import client as HTTPStatus from gi.repository import Gtk from tryton.common import ( IconFactory, RPCException, play_sound, process_exception) from tryton.common.underline import set_underline from tryton.config import CONFIG, TRYTON_ICON from tryton.exceptions import TrytonServerError from tryton.gui import Main from tryton.gui.window.nomodal import NoModal _ = gettext.gettext class CodeScanner(NoModal): def __init__(self, callback, loop=False): super().__init__() self.callback = callback self.loop = loop self.dialog = Gtk.MessageDialog( transient_for=self.parent, destroy_with_parent=True, text=_("Code Scanner")) Main().add_window(self.dialog) self.dialog.set_position(Gtk.WindowPosition.CENTER_ON_PARENT) self.dialog.set_icon(TRYTON_ICON) self.dialog.connect('response', self.response) self.dialog.set_title(_("Code Scanner")) self.entry = Gtk.Entry() self.entry.set_activates_default(True) self.entry.set_placeholder_text(_("Code")) self.dialog.get_message_area().pack_start( self.entry, expand=False, fill=False, padding=9) button_close = self.dialog.add_button( set_underline(_("Close")), Gtk.ResponseType.CLOSE) button_close.set_image(IconFactory.get_image( 'tryton-close', Gtk.IconSize.BUTTON)) button_ok = self.dialog.add_button( set_underline(_("OK")), Gtk.ResponseType.OK) button_ok.set_image(IconFactory.get_image( 'tryton-ok', Gtk.IconSize.BUTTON)) self.dialog.set_default_response(Gtk.ResponseType.OK) self.dialog.show_all() self.register() self.entry.grab_focus() def _play(self, sound): if CONFIG['client.code_scanner_sound']: play_sound(sound) def response(self, dialog, response): if response == Gtk.ResponseType.OK: code = self.entry.get_text() self.entry.set_text('') if code: while True: try: modified = self.callback(code) self._play('success') if not self.loop or not modified: self.destroy() except Exception as exception: unauthorized = ( isinstance(exception, TrytonServerError) and exception.faultCode == str( int(HTTPStatus.UNAUTHORIZED))) if not unauthorized: self._play('danger') try: process_exception(exception) except RPCException: pass if unauthorized: continue self.destroy() return if not self.loop or response != Gtk.ResponseType.OK: self.destroy() def destroy(self): super().destroy() self.dialog.destroy() def show(self): self.dialog.show() def hide(self): self.dialog.hide() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1707150267.0 tryton-7.0.24/tryton/gui/window/dblogin.py0000644000175000017500000007254414560205673016721 0ustar00cedced# This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. import configparser import gettext import logging import os import re import shutil import tempfile import threading from gi.repository import GLib, GObject, Gtk import tryton.common as common import tryton.rpc as rpc from tryton import __version__ from tryton.common.underline import set_underline from tryton.config import CONFIG, PIXMAPS_DIR, TRYTON_ICON, get_config_dir _ = gettext.gettext logger = logging.getLogger(__name__) DEMO_HOSTNAME = re.compile(r"demo\d+\.\d+\.tryton\.org") class DBListEditor(object): def __init__(self, parent, profile_store, profiles, callback): self.profiles = profiles self.current_database = None self.old_profile, self.current_profile = None, None self.db_cache = None self.updating_db = False # GTK Stuffs self.parent = parent self.dialog = Gtk.Dialog( title=_('Profile Editor'), transient_for=parent, modal=True, destroy_with_parent=True) self.ok_button = self.dialog.add_button( set_underline(_("Close")), Gtk.ResponseType.CLOSE) self.dialog.set_position(Gtk.WindowPosition.CENTER_ON_PARENT) self.dialog.set_icon(TRYTON_ICON) common.setup_window(self.dialog) tooltips = common.Tooltips() hpaned = Gtk.HPaned() vbox_profiles = Gtk.VBox(homogeneous=False, spacing=6) self.cell = Gtk.CellRendererText() self.cell.set_property('editable', True) self.cell.connect('editing-started', self.edit_started) self.profile_tree = Gtk.TreeView() self.profile_tree.set_model(profile_store) self.profile_tree.insert_column_with_attributes(-1, _('Profile'), self.cell, text=0) self.profile_tree.connect('cursor-changed', self.profile_selected) scroll = Gtk.ScrolledWindow() scroll.set_policy( Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC) scroll.add(self.profile_tree) self.add_button = Gtk.Button() self.add_button.set_image(common.IconFactory.get_image( 'tryton-add', Gtk.IconSize.BUTTON)) tooltips.set_tip(self.add_button, _("Add new profile")) self.add_button.connect('clicked', self.profile_create) self.remove_button = Gtk.Button() self.remove_button.set_image(common.IconFactory.get_image( 'tryton-remove', Gtk.IconSize.BUTTON)) tooltips.set_tip(self.remove_button, _("Remove selected profile")) self.remove_button.get_style_context().add_class( Gtk.STYLE_CLASS_DESTRUCTIVE_ACTION) self.remove_button.connect('clicked', self.profile_delete) bbox = Gtk.ButtonBox() bbox.pack_start(self.remove_button, expand=True, fill=True, padding=0) bbox.pack_start(self.add_button, expand=True, fill=True, padding=0) vbox_profiles.pack_start(scroll, expand=True, fill=True, padding=0) vbox_profiles.pack_start(bbox, expand=False, fill=True, padding=0) hpaned.add1(vbox_profiles) grid = Gtk.Grid(column_spacing=3, row_spacing=3) host = Gtk.Label( label=set_underline(_('Host:')), use_underline=True, halign=Gtk.Align.END) self.host_entry = Gtk.Entry(hexpand=True) self.host_entry.connect('focus-out-event', self.display_dbwidget) self.host_entry.connect('changed', self.update_profiles, 'host') self.host_entry.set_activates_default(True) host.set_mnemonic_widget(self.host_entry) grid.attach(host, 0, 1, 1, 1) grid.attach(self.host_entry, 1, 1, 1, 1) database = Gtk.Label( label=set_underline(_('Database:')), use_underline=True, halign=Gtk.Align.END) self.database_entry = Gtk.Entry() self.database_entry.connect('changed', self.dbentry_changed) self.database_entry.connect('changed', self.update_profiles, 'database') self.database_entry.set_activates_default(True) self.database_label = Gtk.Label(valign=Gtk.Align.START) self.database_label.set_use_markup(True) self.database_combo = Gtk.ComboBoxText() self.database_combo.connect('changed', self.dbcombo_changed) self.database_progressbar = Gtk.ProgressBar() self.database_progressbar.set_text(_('Fetching databases list')) db_box = Gtk.VBox(homogeneous=True, hexpand=True) db_box.pack_start( self.database_entry, expand=True, fill=True, padding=0) db_box.pack_start( self.database_combo, expand=True, fill=True, padding=0) db_box.pack_start( self.database_label, expand=True, fill=True, padding=0) db_box.pack_start( self.database_progressbar, expand=True, fill=True, padding=0) # Compute size_request of box in order to prevent "form jumping" width, height = 0, 0 for child in db_box.get_children(): request = child.get_preferred_size()[0] width = max(width, request.width) height = max(height, request.height) db_box.set_size_request(width, height) grid.attach(database, 0, 2, 1, 1) grid.attach(db_box, 1, 2, 1, 1) username = Gtk.Label( label=set_underline(_('Username:')), use_underline=True, halign=Gtk.Align.END) self.username_entry = Gtk.Entry(hexpand=True) self.username_entry.connect('changed', self.update_profiles, 'username') username.set_mnemonic_widget(self.username_entry) self.username_entry.set_activates_default(True) grid.attach(username, 0, 3, 1, 1) grid.attach(self.username_entry, 1, 3, 1, 1) hpaned.add2(grid) hpaned.set_position(250) self.dialog.vbox.pack_start(hpaned, expand=True, fill=True, padding=0) self.dialog.set_default_size(640, 350) self.dialog.set_default_response(Gtk.ResponseType.CLOSE) self.dialog.connect('close', lambda *a: False) self.dialog.connect('response', self.response) self.callback = callback def response(self, widget, response): if self.callback: self.callback(self.current_profile['name']) self.parent.present() self.dialog.destroy() def run(self, profile_name): self.clear_entries() # must be done before show_all for windows self.dialog.show_all() model = self.profile_tree.get_model() if model: for i, row in enumerate(model): if row[0] == profile_name: break else: i = 0 self.profile_tree.get_selection().select_path((i,)) self.profile_selected(self.profile_tree) def _current_profile(self): model, selection = self.profile_tree.get_selection().get_selected() if not selection: return {'name': None, 'iter': None} return {'name': model[selection][0], 'iter': selection} def clear_entries(self): for entryname in ('host', 'database', 'username'): entry = getattr(self, '%s_entry' % entryname) entry.handler_block_by_func(self.update_profiles) entry.set_text('') entry.handler_unblock_by_func(self.update_profiles) self.current_database = None self.database_combo.set_active(-1) self.database_combo.get_model().clear() self.hide_database_info() def hide_database_info(self): self.database_entry.hide() self.database_combo.hide() self.database_label.hide() self.database_progressbar.hide() def profile_create(self, button): self.clear_entries() model = self.profile_tree.get_model() model.append(['', False]) column = self.profile_tree.get_column(0) self.profile_tree.set_cursor(len(model) - 1, column, start_editing=True) self.db_cache = None def profile_delete(self, button): self.clear_entries() model, selection = self.profile_tree.get_selection().get_selected() if not selection: return profile_name = model[selection][0] self.profiles.remove_section(profile_name) del model[selection] def profile_selected(self, treeview): self.old_profile = self.current_profile self.current_profile = self._current_profile() if not self.current_profile['name']: return if self.updating_db: self.current_profile = self.old_profile selection = treeview.get_selection() selection.select_iter(self.old_profile['iter']) return fields = ('host', 'database', 'username') for field in fields: entry = getattr(self, '%s_entry' % field) try: entry_value = self.profiles.get(self.current_profile['name'], field) except configparser.NoOptionError: entry_value = '' entry.set_text(entry_value) if field == 'database': self.current_database = entry_value self.display_dbwidget(None, None) def edit_started(self, renderer, editable, path): if isinstance(editable, Gtk.Entry): editable.connect('focus-out-event', self.edit_profilename, renderer, path) def edit_profilename(self, editable, event, renderer, path): newtext = editable.get_text() model = self.profile_tree.get_model() try: oldname = model[path][0] except IndexError: return if oldname == newtext == '': del model[path] return elif oldname == newtext or newtext == '': return elif newtext in self.profiles.sections(): if not oldname: del model[path] return elif oldname in self.profiles.sections(): self.profiles.add_section(newtext) for itemname, value in self.profiles.items(oldname): self.profiles.set(newtext, itemname, value) self.profiles.remove_section(oldname) model[path][0] = newtext else: model[path][0] = newtext self.profiles.add_section(newtext) self.current_profile = self._current_profile() self.host_entry.grab_focus() def update_profiles(self, editable, entryname): new_value = editable.get_text() if not new_value: return section = self._current_profile()['name'] self.profiles.set(section, entryname, new_value) self.validate_profile(section) def validate_profile(self, profile_name): model, selection = self.profile_tree.get_selection().get_selected() if not selection: return active = all(self.profiles.has_option(profile_name, option) for option in ('host', 'database')) model[selection][1] = active @classmethod def test_server_version(cls, host, port): ''' Tests if the server version is compatible with the client version It returns None if no information on server version is available. ''' version = rpc.server_version(host, port) if not version: return None return version.split('.')[:2] == __version__.split('.')[:2] def refresh_databases(self, host, port): self.dbs_updated = threading.Event() if CONFIG['thread']: threading.Thread(target=self.refresh_databases_start, args=(host, port)).start() GLib.timeout_add(100, self.refresh_databases_end, host, port) else: self.refresh_databases_start(host, port) self.refresh_databases_end(host, port) def refresh_databases_start(self, host, port): dbs = None try: dbs = rpc.db_list(host, port) except Exception: pass finally: self.dbs = dbs self.dbs_updated.set() def refresh_databases_end(self, host, port): if not self.dbs_updated.isSet(): self.database_progressbar.show() self.database_progressbar.pulse() return True self.database_progressbar.hide() dbs = self.dbs label = None if self.test_server_version(host, port) is False: label = _('Incompatible version of the server.') elif dbs is None: label = _('Could not connect to the server.') if label: self.database_label.set_label( '%s' % GLib.markup_escape_text(label)) self.database_label.show() else: self.database_combo.remove_all() index = -1 for db_num, db_name in enumerate(dbs): self.database_combo.append_text(db_name) if self.current_database and db_name == self.current_database: index = db_num if index == -1: index = 0 self.database_combo.set_active(index) self.database_entry.set_text(self.current_database if self.current_database else '') if dbs: self.database_combo.show() else: self.database_entry.show() self.db_cache = (host, port, self.current_profile['name']) self.add_button.set_sensitive(True) self.remove_button.set_sensitive(True) self.ok_button.set_sensitive(True) self.cell.set_property('editable', True) self.host_entry.set_sensitive(True) self.updating_db = False return False def display_dbwidget(self, entry, event): netloc = self.host_entry.get_text() host = common.get_hostname(netloc) if not host: return port = common.get_port(netloc) if (host, port, self.current_profile['name']) == self.db_cache: return if self.updating_db: return self.hide_database_info() self.add_button.set_sensitive(False) self.remove_button.set_sensitive(False) self.ok_button.set_sensitive(False) self.cell.set_property('editable', False) self.host_entry.set_sensitive(False) self.updating_db = True self.refresh_databases(host, port) def dbcombo_changed(self, combobox): dbname = combobox.get_active_text() if dbname: self.current_database = dbname self.profiles.set(self.current_profile['name'], 'database', dbname) self.validate_profile(self.current_profile['name']) def dbentry_changed(self, entry): dbname = entry.get_text() if dbname: self.current_database = dbname self.profiles.set(self.current_profile['name'], 'database', dbname) self.validate_profile(self.current_profile['name']) def insert_text_port(self, entry, new_text, new_text_length, position): value = entry.get_text() position = entry.get_position() new_value = value[:position] + new_text + value[position:] try: int(new_value) except ValueError: entry.stop_emission_by_name('insert-text') class DBLogin(object): def __init__(self): # Fake windows to avoid warning about Dialog without transient self._window = Gtk.Window() self.dialog = Gtk.Dialog(title="Tryton - " + _('Login'), modal=True) self.dialog.set_transient_for(self._window) self.dialog.set_icon(TRYTON_ICON) self.dialog.set_position(Gtk.WindowPosition.CENTER_ALWAYS) self.dialog.set_resizable(False) common.setup_window(self.dialog) tooltips = common.Tooltips() button_cancel = Gtk.Button(label=_('_Cancel'), use_underline=True) tooltips.set_tip(button_cancel, _('Cancel connection to the Tryton server')) self.dialog.add_action_widget(button_cancel, Gtk.ResponseType.CANCEL) self.button_connect = Gtk.Button( label=_('C_onnect'), use_underline=True) self.button_connect.get_style_context().add_class( Gtk.STYLE_CLASS_SUGGESTED_ACTION) self.button_connect.set_can_default(True) tooltips.set_tip(self.button_connect, _('Connect the Tryton server')) self.dialog.add_action_widget(self.button_connect, Gtk.ResponseType.OK) self.dialog.set_default_response(Gtk.ResponseType.OK) grid = Gtk.Grid( column_spacing=3, row_spacing=3, valign=Gtk.Align.START) self.dialog.vbox.pack_start(grid, expand=True, fill=True, padding=0) image = Gtk.Image() image.set_from_file(os.path.join(PIXMAPS_DIR, 'tryton.svg')) image.set_valign(Gtk.Align.START) overlay = Gtk.Overlay() overlay.add(image) label = Gtk.Label( label='%s' % __version__, use_markup=True) label.props.halign = Gtk.Align.END label.props.valign = Gtk.Align.START label.props.margin_right = 10 label.props.margin_top = 5 overlay.add_overlay(label) grid.attach(overlay, 0, 0, 3, 1) self.profile_store = Gtk.ListStore( GObject.TYPE_STRING, GObject.TYPE_BOOLEAN) self.combo_profile = Gtk.ComboBox(hexpand=True) cell = Gtk.CellRendererText() self.combo_profile.pack_start(cell, expand=True) self.combo_profile.add_attribute(cell, 'text', 0) self.combo_profile.add_attribute(cell, 'sensitive', 1) self.combo_profile.set_model(self.profile_store) self.combo_profile.connect('changed', self.profile_changed) self.profile_label = Gtk.Label( label=set_underline(_('Profile:')), use_underline=True, halign=Gtk.Align.END) self.profile_label.set_mnemonic_widget(self.combo_profile) self.profile_button = Gtk.Button( label=set_underline(_('Manage...')), use_underline=True) self.profile_button.connect('clicked', self.profile_manage) grid.attach(self.profile_label, 0, 1, 1, 1) grid.attach(self.combo_profile, 1, 1, 1, 1) grid.attach(self.profile_button, 2, 1, 1, 1) self.expander = Gtk.Expander() self.expander.set_label(_('Host / Database information')) self.expander.connect('notify::expanded', self.expand_hostspec) grid.attach(self.expander, 0, 2, 3, 1) self.label_host = Gtk.Label( label=set_underline(_('Host:')), use_underline=True, halign=Gtk.Align.END) self.entry_host = Gtk.Entry(hexpand=True) self.entry_host.connect_after('focus-out-event', self.clear_profile_combo) self.entry_host.connect('focus-out-event', self.update_services) self.entry_host.set_activates_default(True) self.label_host.set_mnemonic_widget(self.entry_host) grid.attach(self.label_host, 0, 3, 1, 1) grid.attach(self.entry_host, 1, 3, 2, 1) self.label_database = Gtk.Label( label=set_underline(_('Database:')), use_underline=True, halign=Gtk.Align.END) self.entry_database = Gtk.Entry(hexpand=True) self.entry_database.connect_after('focus-out-event', self.clear_profile_combo) self.entry_database.set_activates_default(True) self.label_database.set_mnemonic_widget(self.entry_database) grid.attach(self.label_database, 0, 4, 1, 1) grid.attach(self.entry_database, 1, 4, 2, 1) self.entry_login = Gtk.Entry(hexpand=True) self.entry_login.set_activates_default(True) grid.attach(self.entry_login, 1, 5, 2, 1) label_username = Gtk.Label( label=set_underline(_("User name:")), use_underline=True, halign=Gtk.Align.END, margin=3) label_username.set_mnemonic_widget(self.entry_login) grid.attach(label_username, 0, 5, 1, 1) # Profile information config_dir = get_config_dir() self.profile_cfg = os.path.join(config_dir, 'profiles.cfg') self.profiles = configparser.ConfigParser() series = '.'.join(__version__.split('.', 2)[:2]) current_demo = f"demo{series}.tryton.org" try: self.profiles.read(self.profile_cfg) except configparser.Error: # reset self.profiles as parsing errors may leave wrong data in # the parser self.profiles = configparser.ConfigParser() with tempfile.NamedTemporaryFile( delete=False, prefix='profiles_', suffix='.cfg', dir=config_dir) as temp_file: temp_name = temp_file.name shutil.copy(self.profile_cfg, temp_name) logger.error( f"Failed to parse {self.profiles_cfg}. " f"A backup can be found at {temp_name}", exc_info=True) if not self.profiles.sections(): self.profiles.add_section(current_demo) self.profiles.set(current_demo, 'host', current_demo) self.profiles.set(current_demo, 'database', f'demo{series}') self.profiles.set(current_demo, 'username', 'demo') to_remove = [] for section in self.profiles.sections(): host = self.profiles.get(section, 'host', fallback='') if host and DEMO_HOSTNAME.match(host) and host != current_demo: to_remove.append(section) continue active = all(self.profiles.has_option(section, option) for option in ('host', 'database')) self.profile_store.append([section, active]) for section in to_remove: self.profiles.remove_section(section) if to_remove and not self.profiles.has_section(current_demo): self.profiles.add_section(current_demo) self.profiles.set(current_demo, 'host', current_demo) self.profiles.set(current_demo, 'database', f'demo{series}') self.profiles.set(current_demo, 'username', 'demo') self.profile_store.append([current_demo, True]) self.services = [] self.service_base = '' self._services = {} def profile_manage(self, widget): def callback(profile_name): with open(self.profile_cfg, 'w') as configfile: self.profiles.write(configfile) for idx, row in enumerate(self.profile_store): if row[0] == profile_name and row[1]: self.combo_profile.set_active(idx) self.profile_changed(self.combo_profile) break dia = DBListEditor(self.dialog, self.profile_store, self.profiles, callback) active_profile = self.combo_profile.get_active() profile_name = None if active_profile != -1: profile_name = self.profile_store[active_profile][0] dia.run(profile_name) def profile_changed(self, combobox): position = combobox.get_active() if position == -1: return profile = self.profile_store[position][0] try: username = self.profiles.get(profile, 'username') except configparser.NoOptionError: username = '' host = self.profiles.get(profile, 'host') self.entry_host.set_text('%s' % host) self.entry_database.set_text(self.profiles.get(profile, 'database')) if username: self.entry_login.set_text(username) else: self.entry_login.set_text('') self.update_services() def clear_profile_combo(self, *args): netloc = self.entry_host.get_text() host = common.get_hostname(netloc) port = common.get_port(netloc) database = self.entry_database.get_text().strip() login = self.entry_login.get_text() for idx, profile_info in enumerate(self.profile_store): if not profile_info[1]: continue profile = profile_info[0] try: profile_host = self.profiles.get(profile, 'host') profile_db = self.profiles.get(profile, 'database') profile_login = self.profiles.get(profile, 'username') except configparser.NoOptionError: continue if (host == common.get_hostname(profile_host) and port == common.get_port(profile_host) and database == profile_db and (not login or login == profile_login)): break else: idx = -1 self.combo_profile.set_active(idx) return False def expand_hostspec(self, expander, *args): visibility = expander.props.expanded self.entry_host.props.visible = visibility self.label_host.props.visible = visibility self.entry_database.props.visible = visibility self.label_database.props.visible = visibility def update_services(self, *args): host = self.entry_host.get_text() hostname = common.get_hostname(host) port = common.get_port(host) key = (hostname, port) for response_id, service in enumerate(self.services, 1): button = self.dialog.get_widget_for_response(response_id) button.get_parent().remove(button) self.services = [] if key in self._services: self.services, self.service_base = self._services[key] elif hostname and port: self.service_base, self.services = rpc.authentication_services( hostname, port) self._services[key] = (self.services, self.service_base) for response_id, (name, url) in enumerate(self.services, 1): button = Gtk.Button(label=name) self.dialog.add_action_widget(button, response_id) button.show() # Re-add connect button to be last button_connect = self.dialog.get_widget_for_response( Gtk.ResponseType.OK) button_connect.get_parent().remove(button_connect) self.dialog.add_action_widget(button_connect, Gtk.ResponseType.OK) self.dialog.set_default_response(Gtk.ResponseType.OK) def run(self): profile_name = CONFIG['login.profile'] can_use_profile = self.profiles.has_section(profile_name) if can_use_profile: for (configname, option) in [ ('login.host', 'host'), ('login.db', 'database'), ]: try: value = self.profiles.get(profile_name, option) except configparser.NoOptionError: value = None if value != CONFIG[configname]: can_use_profile = False break if can_use_profile: for idx, row in enumerate(self.profile_store): if row[0] == profile_name: self.combo_profile.set_active(idx) break else: self.combo_profile.set_active(-1) host = CONFIG['login.host'] if CONFIG['login.host'] else '' self.entry_host.set_text(host) db = CONFIG['login.db'] if CONFIG['login.db'] else '' self.entry_database.set_text(db) self.entry_login.set_text(CONFIG['login.login']) self.clear_profile_combo() self.update_services() self.dialog.show_all() self.entry_login.grab_focus() self.expander.set_expanded(CONFIG['login.expanded']) # The previous action did not called expand_hostspec self.expand_hostspec(self.expander) response, result = None, ('', '', '', '') while not all(result): response = self.dialog.run() if response != Gtk.ResponseType.OK and response <= 0: break self.clear_profile_combo() active_profile = self.combo_profile.get_active() if active_profile != -1: profile = self.profile_store[active_profile][0] else: profile = '' host = self.entry_host.get_text() hostname = common.get_hostname(host) port = common.get_port(host) test = DBListEditor.test_server_version(hostname, port) if not test: if test is False: common.warning('', _('Incompatible version of the server.'), parent=self.dialog) else: common.warning('', _('Could not connect to the server.'), parent=self.dialog) continue database = self.entry_database.get_text() CONFIG['login.profile'] = profile CONFIG['login.host'] = host CONFIG['login.db'] = database CONFIG['login.expanded'] = self.expander.props.expanded if response == Gtk.ResponseType.OK: authentication = self.entry_login.get_text() CONFIG['login.login'] = authentication CONFIG['login.service'] = '' else: authentication = self.services[response - 1][1] authentication = ( self.service_base + database + authentication) CONFIG['login.login'] = '' CONFIG['login.service'] = authentication result = (hostname, port, database, authentication) self.dialog.destroy() self._window.destroy() return response == Gtk.ResponseType.OK or response > 0 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685599.0 tryton-7.0.24/tryton/gui/window/email_.py0000644000175000017500000003240614517761237016527 0ustar00cedced# This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. import gettext import logging import os from gi.repository import GLib, GObject, Gtk try: from gi.repository import GtkSpell except ImportError: GtkSpell = None from tryton.common import IconFactory, RPCException, RPCExecute, Tooltips from tryton.common.richtext import ( add_toolbar, get_content, register_format, set_content) from tryton.common.underline import set_underline from tryton.common.widget_style import widget_class from tryton.config import CONFIG, TRYTON_ICON from tryton.exceptions import TrytonError, TrytonServerError from tryton.gui import Main from tryton.gui.window.nomodal import NoModal _ = gettext.gettext logger = logging.getLogger(__name__) class EmailEntry(Gtk.Entry): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self._completion = Gtk.EntryCompletion() self._completion.set_model(Gtk.ListStore(str, str)) self._completion.set_text_column(0) self._completion.set_match_func(lambda *a: True) self._completion.connect('match-selected', self._match_selected) self.set_completion(self._completion) self.connect('changed', EmailEntry._changed) def _match_selected(self, completion, model, iter): self.set_text(model.get_value(iter, 1)) self.set_position(-1) return True def _update_completion(self, text): if not self.props.window: return False if text != self.get_text(): return False model = self._completion.get_model() if not text: model.clear() model.text = text return False if getattr(model, 'text', None) == text: return False def callback(results): try: results = results() except (TrytonError, TrytonServerError): logger.warning( "Unable to complete email entry", exc_info=True) results = [] if text != self.get_text(): return False model.clear() for ratio, address, addresses in results: model.append([address, addresses]) model.text = text # Force display of popup self.emit('changed') try: RPCExecute( 'model', 'ir.email', 'complete', text, CONFIG['client.limit'], process_exception=False, callback=callback) except Exception: logger.warning( _("Unable to complete email entry"), exc_info=True) return False def _changed(self): def keypress(): if not self.props.window: return self._update_completion() text = self.get_text() if self.get_position() >= len(text) - 1: GLib.timeout_add(300, self._update_completion, text) else: model = self._completion.get_model() model.clear() model.text = None class Email(NoModal): def __init__(self, name, record, prints, template=None): super().__init__() self.record = record self.dialog = Gtk.Dialog( transient_for=self.parent, destroy_with_parent=True) Main().add_window(self.dialog) self.dialog.set_position(Gtk.WindowPosition.CENTER_ON_PARENT) self.dialog.set_icon(TRYTON_ICON) self.dialog.set_default_size(*self.default_size()) self.dialog.connect('response', self.response) self.dialog.set_title(_('E-mail %s') % name) grid = Gtk.Grid( column_spacing=3, row_spacing=3, border_width=3) self.dialog.vbox.pack_start(grid, expand=True, fill=True, padding=0) label = Gtk.Label( set_underline(_("To:")), use_underline=True, halign=Gtk.Align.END) grid.attach(label, 0, 0, 1, 1) self.to = EmailEntry(hexpand=True, activates_default=True) widget_class(self.to, 'required', True) label.set_mnemonic_widget(self.to) grid.attach(self.to, 1, 0, 1, 1) label = Gtk.Label( set_underline(_("Cc:")), use_underline=True, halign=Gtk.Align.END) grid.attach(label, 0, 1, 1, 1) self.cc = EmailEntry(hexpand=True, activates_default=True) label.set_mnemonic_widget(self.cc) grid.attach(self.cc, 1, 1, 1, 1) label = Gtk.Label( set_underline(_("Bcc:")), use_underline=True, halign=Gtk.Align.END) grid.attach(label, 0, 2, 1, 1) self.bcc = EmailEntry(hexpand=True, activates_default=True) label.set_mnemonic_widget(self.bcc) grid.attach(self.bcc, 1, 2, 1, 1) label = Gtk.Label( set_underline(_("Subject:")), use_underline=True, halign=Gtk.Align.END) grid.attach(label, 0, 3, 1, 1) self.subject = Gtk.Entry(hexpand=True, activates_default=True) label.set_mnemonic_widget(self.subject) grid.attach(self.subject, 1, 3, 1, 1) self.body = Gtk.TextView() body_frame = Gtk.Frame() label = Gtk.Label( set_underline(_("Body")), use_underline=True, halign=Gtk.Align.END) label.set_mnemonic_widget(self.body) body_frame.set_label_widget(label) grid.attach(body_frame, 0, 4, 2, 1) body_box = Gtk.VBox(hexpand=True, vexpand=True) body_frame.add(body_box) register_format(self.body) body_toolbar = add_toolbar(self.body) body_box.pack_start(body_toolbar, expand=False, fill=True, padding=0) body_box.pack_start(self.body, expand=True, fill=True, padding=0) if GtkSpell and CONFIG['client.spellcheck']: checker = GtkSpell.Checker() checker.attach(self.body) language = os.environ.get('LANGUAGE', 'en') try: checker.set_language(language) except Exception: logger.error( 'Could not set spell checker for "%s"', language) checker.detach() attachments_box = Gtk.HBox() grid.attach(attachments_box, 0, 5, 2, 1) print_frame = Gtk.Frame(shadow_type=Gtk.ShadowType.NONE) print_frame.set_label(_("Reports")) attachments_box.pack_start( print_frame, expand=True, fill=True, padding=0) print_box = Gtk.VBox() print_frame.add(print_box) print_flowbox = Gtk.FlowBox(selection_mode=Gtk.SelectionMode.NONE) print_box.pack_start( print_flowbox, expand=False, fill=False, padding=0) self.print_actions = {} for print_ in prints: print_check = Gtk.CheckButton.new_with_mnemonic( set_underline(print_['name'])) self.print_actions[print_['id']] = print_check print_flowbox.add(print_check) attachment_frame = Gtk.Frame(shadow_type=Gtk.ShadowType.NONE) attachment_frame.set_label(_("Attachments")) attachments_box.pack_start( attachment_frame, expand=True, fill=True, padding=0) try: attachments = RPCExecute('model', 'ir.attachment', 'search_read', [ ('resource', '=', '%s,%s' % ( record.model_name, record.id)), ['OR', ('data', '!=', None), ('file_id', '!=', None), ], ], 0, None, None, ['rec_name'], context=record.get_context()) except RPCException: logger.error( 'Could not fetch attachment for "%s"', record) attachments = [] scrolledwindow = Gtk.ScrolledWindow() if len(attachments) > 2: scrolledwindow.set_size_request(-1, 100) attachment_frame.add(scrolledwindow) scrolledwindow.set_policy( Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC) self.attachments = Gtk.TreeView() self.attachments.set_headers_visible(False) scrolledwindow.add(self.attachments) self.attachments.get_selection().set_mode(Gtk.SelectionMode.MULTIPLE) self.attachments.append_column( Gtk.TreeViewColumn( "Name", Gtk.CellRendererText(), text=0)) model = Gtk.ListStore(GObject.TYPE_STRING, GObject.TYPE_INT) for attachment in attachments: model.append((attachment['rec_name'], attachment['id'])) self.attachments.set_model(model) self.attachments.set_search_column(0) file_frame = Gtk.Frame(shadow_type=Gtk.ShadowType.NONE) file_frame.set_label(_("Files")) attachments_box.pack_start( file_frame, expand=True, fill=True, padding=0) self.files = Gtk.VBox(spacing=6) file_frame.add(self.files) self._add_file_button() button_cancel = self.dialog.add_button( set_underline(_("Cancel")), Gtk.ResponseType.CANCEL) button_cancel.set_image(IconFactory.get_image( 'tryton-cancel', Gtk.IconSize.BUTTON)) button_send = self.dialog.add_button( set_underline(_("Send")), Gtk.ResponseType.OK) button_send.set_image(IconFactory.get_image( 'tryton-send', Gtk.IconSize.BUTTON)) self.dialog.set_default_response(Gtk.ResponseType.OK) self._fill_with(template) self.dialog.show_all() self.register() def _add_file_button(self): tooltips = Tooltips() box = Gtk.HBox(spacing=3) self.files.pack_start(box, expand=False, fill=True, padding=0) file_ = Gtk.FileChooserButton(title=_("Select File")) box.pack_start(file_, expand=True, fill=True, padding=0) button = Gtk.Button() button.set_image(IconFactory.get_image( 'tryton-remove', Gtk.IconSize.BUTTON)) tooltips.set_tip(button, _("Remove File")) button.set_sensitive(False) box.pack_start(button, expand=False, fill=True, padding=0) box.show_all() file_.connect('file-set', self._file_set, button) button.connect('clicked', self._file_remove) def _file_set(self, file_, button): button.set_sensitive(True) self._add_file_button() def _file_remove(self, button): self.files.remove(button.get_parent()) def get_files(self): for box in self.files: file_ = list(box)[0] filename = file_.get_filename() if not filename: continue with open(filename, 'rb') as fp: data = fp.read() name = os.path.basename(filename) yield (name, data) def get_attachments(self): model, paths = self.attachments.get_selection().get_selected_rows() return [model[path][1] for path in paths] def _fill_with(self, template=None): try: if template: values = RPCExecute( 'model', 'ir.email.template', 'get', template, self.record.id) else: values = RPCExecute( 'model', 'ir.email.template', 'get_default', self.record.model_name, self.record.id) except RPCException: return self.to.set_text(', '.join(values.get('to', []))) self.cc.set_text(', '.join(values.get('cc', []))) self.bcc.set_text(', '.join(values.get('bcc', []))) self.subject.set_text(values.get('subject', '')) set_content(self.body, values.get('body', '')) print_ids = values.get('reports', []) for print_id, print_check in self.print_actions.items(): print_check.set_active(print_id in print_ids) def validate(self): valid = True if not self.subject.get_text(): valid = False widget_class(self.subject, 'invalid', True) self.subject.grab_focus() else: widget_class(self.subject, 'invalid', False) if not self.to.get_text(): valid = False widget_class(self.to, 'invalid', True) self.to.grab_focus() else: widget_class(self.to, 'invalid', False) return valid def response(self, dialog, response): if response == Gtk.ResponseType.OK: if not self.validate(): return to = self.to.get_text() cc = self.cc.get_text() bcc = self.bcc.get_text() subject = self.subject.get_text() body = get_content(self.body) files = list(self.get_files()) reports = [ id_ for id_, check in self.print_actions.items() if check.get_active()] attachments = self.get_attachments() try: RPCExecute( 'model', 'ir.email', 'send', to, cc, bcc, subject, body, files, [self.record.model_name, self.record.id], reports, attachments) except RPCException: return self.destroy() def destroy(self): super().destroy() self.dialog.destroy() def show(self): self.dialog.show() def hide(self): self.dialog.hide() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1744651504.0 tryton-7.0.24/tryton/gui/window/form.py0000644000175000017500000010340714777242360016243 0ustar00cedced# This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. "Form" import csv import gettext import locale import os import tempfile from itertools import zip_longest from gi.repository import Gdk, GLib, Gtk import tryton.common as common from tryton import plugins from tryton.action import Action from tryton.common import RPCException, RPCExecute, sur, sur_3b from tryton.common.common import selection as selection_ from tryton.common.popup_menu import popup from tryton.common.underline import set_underline from tryton.gui import Main from tryton.gui.window import Window from tryton.gui.window.attachment import Attachment from tryton.gui.window.email_ import Email from tryton.gui.window.log import Log from tryton.gui.window.note import Note from tryton.gui.window.revision import Revision from tryton.gui.window.view_form.screen import Screen from tryton.gui.window.win_export import WinExport from tryton.gui.window.win_import import WinImport from .tabcontent import TabContent _ = gettext.gettext class Form(TabContent): "Form" def __init__(self, model, res_id=None, name='', **attributes): super(Form, self).__init__(**attributes) self.model = model self.res_id = res_id self.mode = attributes.get('mode') self.view_ids = attributes.get('view_ids') self.dialogs = [] if not name: name = common.MODELNAME.get(model) self.name = name loading_ids = res_id not in (None, False) if loading_ids: attributes.pop('tab_domain', None) self.screen = Screen(self.model, breadcrumb=[self.name], **attributes) self.screen.widget.show() self.screen.windows.append(self) self.create_tabcontent() self.set_buttons_sensitive() self.attachment_screen = None if loading_ids: if isinstance(res_id, int): res_id = [res_id] self.screen.load(res_id) if res_id: self.screen.current_record = self.screen.group.get(res_id[0]) self.screen.display() else: if self.screen.current_view.view_type == 'form': self.sig_new(None, autosave=False) if self.screen.current_view.view_type \ in ('tree', 'graph', 'calendar'): self.screen.search_filter() self.update_revision() self.set_buttons_sensitive() def get_toolbars(self): try: return RPCExecute('model', self.model, 'view_toolbar_get', context=self.screen.context) except RPCException: return {} def create_tabcontent(self): super().create_tabcontent() self.attachment_preview = Gtk.Viewport() self.attachment_preview.set_shadow_type(Gtk.ShadowType.NONE) self.attachment_preview.show() scrolledwindow = Gtk.ScrolledWindow() scrolledwindow.set_shadow_type(Gtk.ShadowType.NONE) scrolledwindow.set_policy( Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC) scrolledwindow.add(self.attachment_preview) scrolledwindow.set_size_request(300, -1) self.main.pack2(scrolledwindow, resize=False, shrink=True) def widget_get(self): return self.screen.widget def compare(self, model, attributes): if not attributes: return False return (self.model == model and self.res_id == attributes.get('res_id') and self.attributes.get('domain') == attributes.get('domain') and self.attributes.get('view_ids') == attributes.get('view_ids') and (attributes.get('view_ids') or (self.attributes.get('mode') or ['tree', 'form']) == ( attributes.get('mode') or ['tree', 'form'])) and self.screen.local_context == attributes.get('context') and self.attributes.get('search_value') == ( attributes.get('search_value')) and self.attributes.get('tab_domain') == ( attributes.get('tab_domain'))) def __hash__(self): return id(self) def destroy(self): self.screen.destroy() def sig_attach(self, widget=None): def window(widget): return Attachment( record, lambda: self.refresh_resources(reload=True)) def add_file(widget): filenames = common.file_selection(_("Select"), multi=True) if filenames: attachment = window(widget) for filename in filenames: attachment.add_file(filename) def preview(widget): children = self.attachment_preview.get_children() for child in children: self.attachment_preview.remove(child) if widget.get_active(): self.attachment_preview.add( self._attachment_preview_widget()) self.attachment_preview.get_parent().show() self.refresh_attachment_preview() else: self.attachment_screen = None self.attachment_preview.get_parent().hide() def activate(widget, callback): callback() button = self.buttons['attach'] if widget != button: if button.props.sensitive: button.props.active = True return record = self.screen.current_record menu = button._menu = Gtk.Menu() for name, callback in Attachment.get_attachments(record): item = Gtk.MenuItem(label=name) item.connect('activate', activate, callback) menu.add(item) menu.add(Gtk.SeparatorMenuItem()) add_item = Gtk.MenuItem(label=_("Add...")) add_item.connect('activate', add_file) menu.add(add_item) preview_item = Gtk.CheckMenuItem(label=_("Preview")) preview_item.set_active(bool( self.attachment_preview.get_children())) preview_item.connect('toggled', preview) menu.add(preview_item) manage_item = Gtk.MenuItem(label=_("Manage...")) manage_item.connect('activate', window) menu.add(manage_item) menu.show_all() menu.connect('deactivate', self._popup_menu_hide, button) self.action_popup(button) def _attachment_preview_widget(self): vbox = Gtk.VBox(homogeneous=False, spacing=2) vbox.set_margin_start(4) hbox = Gtk.HBox(homogeneous=False, spacing=0) hbox.set_halign(Gtk.Align.CENTER) vbox.pack_start(hbox, expand=False, fill=True, padding=0) hbox.set_border_width(2) tooltips = common.Tooltips() but_prev = Gtk.Button() tooltips.set_tip(but_prev, _("Previous")) but_prev.add(common.IconFactory.get_image( 'tryton-back', Gtk.IconSize.SMALL_TOOLBAR)) but_prev.set_relief(Gtk.ReliefStyle.NONE) hbox.pack_start(but_prev, expand=False, fill=False, padding=0) label = Gtk.Label(label='(0,0)') hbox.pack_start(label, expand=False, fill=False, padding=0) but_next = Gtk.Button() tooltips.set_tip(but_next, _("Next")) but_next.add(common.IconFactory.get_image( 'tryton-forward', Gtk.IconSize.SMALL_TOOLBAR)) but_next.set_relief(Gtk.ReliefStyle.NONE) hbox.pack_start(but_next, expand=False, fill=False, padding=0) vbox.show_all() self.attachment_screen = screen = Screen( 'ir.attachment', readonly=True, mode=['form'], context={ 'preview': True, }) screen.widget.show() but_prev.connect('clicked', lambda *a: screen.display_prev()) but_next.connect('clicked', lambda *a: screen.display_next()) class Preview(): def record_message(self, position, length, *args): label.set_text('(%s/%s)' % (position or '_', length)) but_prev.set_sensitive(screen.has_prev()) but_next.set_sensitive(screen.has_next()) screen.windows.append(Preview()) vbox.pack_start(screen.widget, expand=True, fill=True, padding=0) return vbox def refresh_attachment_preview(self, force=False): if not self.attachment_screen: return record = self.screen.current_record if not record: return resource = '%s,%s' % (record.model_name, record.id) domain = [ ('resource', '=', resource), ('type', '=', 'data'), ] if self.attachment_screen.domain != domain or force: self.attachment_screen.domain = domain self.attachment_screen.search_filter() group = self.attachment_screen.group if group: self.attachment_screen.current_record = group[0] self.attachment_screen.display() def sig_note(self, widget=None): record = self.screen.current_record if not record or record.id < 0: return Note(record, lambda: self.refresh_resources(reload=True)) def refresh_resources(self, reload=False): record = self.screen.current_record self.update_resources( record.get_resources(reload=reload) if record else None) if reload: self.refresh_attachment_preview(True) def update_resources(self, resources): if not resources: resources = {} record = self.screen.current_record sensitive = record.id >= 0 if record else False def update(name, label, icon, badge): button = self.buttons[name] button.set_label(label) image = common.IconFactory.get_image( icon, Gtk.IconSize.LARGE_TOOLBAR, badge=badge) image.show() button.set_icon_widget(image) button.props.sensitive = sensitive attachment_count = resources.get('attachment_count', 0) badge = 1 if attachment_count else None label = _("Attachment (%s)") % attachment_count update('attach', label, 'tryton-attach', badge) note_count = resources.get('note_count', 0) note_unread = resources.get('note_unread', 0) if note_unread: badge = 2 elif note_count: badge = 1 else: badge = None label = _("Note (%d/%d)") % (note_unread, note_count) update('note', label, 'tryton-note', badge) def sig_switch(self, widget=None): if not self.modified_save(): return self.screen.switch_view() def sig_logs(self, widget=None): current_record = self.screen.current_record if not current_record or current_record.id < 0: self.info_bar_add( _('You have to select one record.'), Gtk.MessageType.INFO) return Log(current_record) def sig_revision(self, widget=None): if not self.modified_save(): return current_id = (self.screen.current_record.id if self.screen.current_record else None) try: revisions = RPCExecute('model', self.model, 'history_revisions', [r.id for r in self.screen.selected_records]) except RPCException: return revision = self.screen.context.get('_datetime') format_ = self.screen.context.get('date_format', '%x') format_ += ' %H:%M:%S.%f' revision = Revision(revisions, revision, format_).run() # Prevent too old revision in form view if (self.screen.current_view.view_type == 'form' and revision and revision < revisions[-1][0]): revision = revisions[-1][0] if revision != self.screen.context.get('_datetime'): self.screen.clear() # Update root group context that will be propagated self.screen.group._context['_datetime'] = revision if self.screen.current_view.view_type != 'form': self.screen.search_filter( self.screen.screen_container.get_text()) else: # Test if record exist in revisions self.screen.load([current_id]) self.screen.display(set_cursor=True) self.update_revision() def update_revision(self): tooltips = common.Tooltips() revision = self.screen.context.get('_datetime') if revision: format_ = self.screen.context.get('date_format', '%x') format_ += ' %H:%M:%S.%f' revision_label = ' @ %s' % revision.strftime(format_) label = common.ellipsize( self.name, 80 - len(revision_label)) + revision_label tooltip = self.name + revision_label else: label = common.ellipsize(self.name, 80) tooltip = self.name self.title.set_text(label) tooltips.set_tip(self.title, tooltip) self.set_buttons_sensitive() def set_buttons_sensitive(self): revision = self.screen.context.get('_datetime') if not revision: access = common.MODELACCESS[self.model] modified = self.screen.modified() for name, sensitive in [ ('new', access['create'] and not modified), ('save', (access['create'] or access['write']) and modified and not self.screen.readonly), ('remove', access['delete']), ('copy', access['create']), ('import', access['create']), ]: if name in self.buttons: self.buttons[name].props.sensitive = sensitive if name in self.menu_buttons: self.menu_buttons[name].props.sensitive = sensitive else: for name in ['new', 'save', 'remove', 'copy', 'import']: if name in self.buttons: self.buttons[name].props.sensitive = False if name in self.menu_buttons: self.menu_buttons[name].props.sensitive = False def sig_remove(self, widget=None): if (not common.MODELACCESS[self.model]['delete'] or not self.screen.deletable): return if self.screen.current_view.view_type == 'form': msg = _('Are you sure to remove this record?') else: msg = _('Are you sure to remove those records?') if sur(msg): if not self.screen.remove(delete=True, force_remove=True): self.info_bar_add( _('Records not removed.'), Gtk.MessageType.ERROR) else: self.info_bar_add(_('Records removed.'), Gtk.MessageType.INFO) self.screen.count_tab_domain(True) def sig_import(self, widget=None): WinImport(self.title.get_text(), self.model, self.screen.context) def sig_export(self, widget=None): if not self.modified_save(): return export = WinExport(self.title.get_text(), self.screen) for name in self.screen.current_view.get_fields(): type = self.screen.group.fields[name].attrs['type'] if type == 'selection': export.sel_field(name + '.translated') elif type == 'reference': export.sel_field(name + '.translated') export.sel_field(name + '/rec_name') else: export.sel_field(name) def do_export(self, widget, export): if not self.modified_save(): return if export.get('records') == 'listed': ids = [r.id for r in self.screen.listed_records] paths = self.screen.listed_paths else: ids = [r.id for r in self.screen.selected_records] paths = self.screen.selected_paths fields = [f['name'] for f in export['export_fields.']] data = RPCExecute( 'model', self.model, 'export_data', ids, fields, export['header'], context=self.screen.context) delimiter = ',' if os.name == 'nt' and ',' == locale.localeconv()['decimal_point']: delimiter = ';' fileno, fname = tempfile.mkstemp( '.csv', common.slugify(export['name']) + '_') with open(fname, 'w', newline='') as fp: writer = csv.writer(fp, delimiter=delimiter) for row, path in zip_longest(data, paths or []): indent = len(path) - 1 if path else 0 if row: writer.writerow(WinExport.format_row(row, indent=indent)) os.close(fileno) common.file_open(fname, 'csv') def sig_new(self, widget=None, autosave=True): if not common.MODELACCESS[self.model]['create']: return if autosave: if not self.modified_save(): return self.screen.new() self.info_bar_clear() self.set_buttons_sensitive() def sig_copy(self, widget=None): if not common.MODELACCESS[self.model]['create']: return if not self.modified_save(): return if self.screen.copy(): self.info_bar_add( _('Working now on the duplicated record(s).'), Gtk.MessageType.INFO) self.screen.count_tab_domain(True) def sig_save(self, widget=None): if widget: # Called from button so we must save the tree state self.screen.save_tree_state() if (self.screen.readonly or not (common.MODELACCESS[self.model]['write'] or common.MODELACCESS[self.model]['create'])): return if self.screen.save_current(): self.info_bar_add(_('Record saved.'), Gtk.MessageType.INFO) self.screen.count_tab_domain(True) return True else: self.info_bar_add( self.screen.invalid_message(), Gtk.MessageType.ERROR) return False def sig_previous(self, widget=None): if not self.modified_save(): return self.screen.display_prev() self.info_bar_clear() self.set_buttons_sensitive() def sig_next(self, widget=None): if not self.modified_save(): return self.screen.display_next() self.info_bar_clear() self.set_buttons_sensitive() def sig_reload(self, test_modified=True): if test_modified: if not self.modified_save(): return False else: self.screen.save_tree_state(store=False) self.screen.cancel_current() set_cursor = False record_id = (self.screen.current_record.id if self.screen.current_record else None) if self.screen.current_view.view_type != 'form': self.screen.search_filter(self.screen.screen_container.get_text()) for record in self.screen.group: if record.id == record_id: self.screen.current_record = record set_cursor = True break self.screen.display(set_cursor=set_cursor) self.info_bar_clear() self.set_buttons_sensitive() self.screen.count_tab_domain() return True def sig_action(self, widget): if self.buttons['action'].props.sensitive: self.buttons['action'].props.active = True def sig_print(self, widget): if self.buttons['print'].props.sensitive: self.buttons['print'].props.active = True def sig_print_open(self, widget): if self.buttons['open'].props.sensitive: self.buttons['open'].props.active = True def sig_email(self, widget): def is_report(action): return action['type'] == 'ir.action.report' if self.buttons['email'].props.sensitive: if not self.modified_save(): return record = self.screen.current_record if not record or record.id < 0: return toolbars = self.get_toolbars() title = self.title.get_text() prints = filter(is_report, toolbars['print']) emails = {e['name']: e['id'] for e in toolbars['emails']} template = selection_(_("Template"), emails, alwaysask=True) if template: template = template[1] Email( '%s: %s' % (title, record.rec_name()), record, prints, template=template) def sig_relate(self, widget): if self.buttons['relate'].props.sensitive: self.buttons['relate'].props.active = True def sig_copy_url(self, widget): if self.buttons['copy_url'].props.sensitive: self.buttons['copy_url'].props.active = True def sig_search(self, widget): search_container = self.screen.screen_container if hasattr(search_container, 'search_entry'): search_container.search_entry.grab_focus() def action_popup(self, widget): button, = widget.get_children() button.grab_focus() menu = widget._menu if not widget.props.active: menu.popdown() return popup(menu, widget) def record_message(self, position, size, max_size, record_id): def set_sensitive(button_id, sensitive): if button_id in self.buttons: self.buttons[button_id].props.sensitive = sensitive if button_id in self.menu_buttons: self.menu_buttons[button_id].props.sensitive = sensitive name = str(position) if position else '_' selected = len(self.screen.selected_records) view_type = self.screen.current_view.view_type next_view_type = self.screen.next_view_type has_views = self.screen.number_of_views > 1 if selected > 1: name += '#%i' % selected for button_id in ['print', 'relate', 'email', 'open', 'attach']: button = self.buttons[button_id] can_be_sensitive = getattr(button, '_can_be_sensitive', True) if button_id in {'print', 'relate', 'email', 'open'}: action_type = button_id if button_id == 'open': action_type = 'print' can_be_sensitive |= any( b.attrs.get('keyword', 'action') == action_type for b in self.screen.get_buttons()) set_sensitive(button_id, bool(position) and can_be_sensitive) set_sensitive( 'switch', (position or view_type == 'form' or next_view_type != 'form') and has_views) set_sensitive('remove', self.screen.deletable) set_sensitive('previous', self.screen.has_prev()) set_sensitive('next', self.screen.has_next()) if size < max_size: msg = "%s@%s/%s" % ( name, common.humanize(size), common.humanize(max_size)) if max_size >= self.screen.count_limit: msg += "+" else: msg = "%s/%s" % (name, common.humanize(size)) self.status_label.set_text(msg) self.info_bar_clear() self.set_buttons_sensitive() self.refresh_attachment_preview() def record_modified(self): def _record_modified(): # As it is called via idle_add, the form could have been destroyed # in the meantime. if self.widget_get().props.window: self.set_buttons_sensitive() GLib.idle_add(_record_modified) self.info_bar_refresh() def record_saved(self): self.set_buttons_sensitive() self.refresh_resources() def modified_save(self): self.screen.save_tree_state() self.screen.current_view.set_value() if self.screen.modified(): value = sur_3b( _('This record has been modified\n' 'do you want to save it?')) if value == 'ok': return self.sig_save(None) if value == 'ko': record_id = self.screen.current_record.id if self.sig_reload(test_modified=False): if record_id < 0: return None elif self.screen.current_record: return record_id == self.screen.current_record.id return False return True def sig_close(self, widget=None): for dialog in reversed(self.dialogs[:]): dialog.destroy() modified_save = self.modified_save() return True if modified_save is None else modified_save def _action(self, action, atype): if not self.modified_save(): return action = action.copy() record_id = (self.screen.current_record.id if self.screen.current_record else None) if action.get('records') == 'listed': record_ids = [r.id for r in self.screen.listed_records] record_paths = self.screen.listed_paths else: record_ids = [r.id for r in self.screen.selected_records] record_paths = self.screen.selected_paths data = { 'model': self.screen.model_name, 'model_context': ( self.screen.context_screen.model_name if self.screen.context_screen else None), 'id': record_id, 'ids': record_ids, 'paths': record_paths, } Action.execute(action, data, context=self.screen.local_context) def sig_win_close(self, widget): Main().sig_win_close(widget) def create_toolbar(self, toolbars): gtktoolbar = super(Form, self).create_toolbar(toolbars) attach_btn = self.buttons['attach'] attach_btn.drag_dest_set( Gtk.DestDefaults.ALL, [ Gtk.TargetEntry.new('text/uri-list', 0, 0), Gtk.TargetEntry.new('text/plain', 0, 0), ], Gdk.DragAction.MOVE | Gdk.DragAction.COPY) attach_btn.connect('drag_data_received', self.attach_drag_data_received) pos = gtktoolbar.get_item_index(self.buttons['email']) iconstock = { 'print': 'tryton-print', 'action': 'tryton-launch', 'relate': 'tryton-link', 'open': 'tryton-open', } for action_type, special_action, action_name, tooltip in ( ('action', 'action', _('Action'), _('Launch action')), ('relate', 'relate', _('Relate'), _('Open related records')), (None,) * 4, ('print', 'open', _('Report'), _('Open report')), ('print', 'print', _('Print'), _('Print report')), ): if action_type is not None: tbutton = Gtk.ToggleToolButton() tbutton.set_icon_widget(common.IconFactory.get_image( iconstock.get(special_action), Gtk.IconSize.LARGE_TOOLBAR)) tbutton.set_label(action_name) tbutton._menu = self._create_popup_menu(tbutton, action_type, toolbars[action_type], special_action) tbutton.connect('toggled', self.action_popup) self.tooltips.set_tip(tbutton, tooltip) self.buttons[special_action] = tbutton if action_type != 'action': tbutton._can_be_sensitive = bool( tbutton._menu.get_children()) else: tbutton = Gtk.SeparatorToolItem() gtktoolbar.insert(tbutton, pos) pos += 1 exports = toolbars['exports'] if exports: tbutton = self.buttons['open'] tbutton._can_be_sensitive = True menu = tbutton._menu if menu.get_children(): menu.add(Gtk.SeparatorMenuItem()) for export in exports: menuitem = Gtk.MenuItem(set_underline(export['name'])) menuitem.set_use_underline(True) menuitem.connect('activate', self.do_export, export) menu.add(menuitem) last_item = gtktoolbar.get_nth_item(gtktoolbar.get_n_items() - 1) if not isinstance(last_item, Gtk.SeparatorToolItem): gtktoolbar.insert(Gtk.SeparatorToolItem(), -1) url_button = Gtk.ToggleToolButton() url_button.set_icon_widget( common.IconFactory.get_image( 'tryton-public', Gtk.IconSize.LARGE_TOOLBAR)) url_button.set_label(_('_Copy URL')) url_button.set_use_underline(True) self.tooltips.set_tip( url_button, _('Copy URL into clipboard')) url_button._menu = url_menu = Gtk.Menu() url_menuitem = Gtk.MenuItem() url_menuitem.connect('activate', self.url_copy) url_menu.add(url_menuitem) url_menu.show_all() url_menu.connect('deactivate', self._popup_menu_hide, url_button) url_button.connect('toggled', self.url_set, url_menuitem) url_button.connect('toggled', self.action_popup) self.buttons['copy_url'] = url_button gtktoolbar.insert(url_button, -1) return gtktoolbar def _create_popup_menu(self, widget, keyword, actions, special_action): menu = Gtk.Menu() menu.connect('deactivate', self._popup_menu_hide, widget) widget.connect('toggled', self._update_popup_menu, menu, keyword) for action in actions: new_action = action.copy() if special_action == 'print': new_action['direct_print'] = True menuitem = Gtk.MenuItem(label=set_underline(action['name'])) menuitem.set_use_underline(True) menuitem.connect('activate', self._popup_menu_selected, widget, new_action, keyword) menu.add(menuitem) return menu def _popup_menu_selected(self, menuitem, togglebutton, action, keyword): event = Gtk.get_current_event() allow_similar = False if (event.state & Gdk.ModifierType.CONTROL_MASK or event.state & Gdk.ModifierType.MOD1_MASK): allow_similar = True with Window(hide_current=True, allow_similar=allow_similar): self._action(action, keyword) togglebutton.props.active = False def _popup_menu_hide(self, menuitem, togglebutton): togglebutton.props.active = False def _update_popup_menu(self, tbutton, menu, keyword): for item in menu.get_children(): if getattr(item, '_update_action', False): menu.remove(item) buttons = [b for b in self.screen.get_buttons() if keyword == b.attrs.get('keyword', 'action')] if buttons and menu.get_children(): separator = Gtk.SeparatorMenuItem() separator._update_action = True menu.add(separator) for button in buttons: menuitem = Gtk.MenuItem( label=set_underline(button.attrs.get('string', _('Unknown'))), use_underline=True) menuitem.connect('activate', lambda m, attrs: self.screen.button(attrs), button.attrs) menuitem._update_action = True menu.add(menuitem) kw_plugins = [] for plugin in plugins.MODULES: for plugin_spec in plugin.get_plugins(self.model): name, func = plugin_spec[:2] try: plugin_keyword = plugin_spec[2] except IndexError: plugin_keyword = 'action' if keyword != plugin_keyword: continue kw_plugins.append((name, func)) if kw_plugins: separator = Gtk.SeparatorMenuItem() separator._update_action = True menu.add(separator) for name, func in kw_plugins: menuitem = Gtk.MenuItem(label=set_underline(name)) menuitem.set_use_underline(True) menuitem.connect('activate', lambda m, func: func({ 'model': self.screen.model_name, 'model_context': ( self.screen.context_screen.model_name if self.screen.context_screen else None), 'id': (self.screen.current_record.id if self.screen.current_record else None), 'ids': [r.id for r in self.screen.selected_records], 'paths': self.screen.selected_paths, }), func) menuitem._update_action = True menu.add(menuitem) def url_copy(self, menuitem): url = self.screen.get_url(self.name) for selection in [ Gdk.Atom.intern('PRIMARY', True), Gdk.Atom.intern('CLIPBOARD', True), ]: clipboard = Gtk.Clipboard.get(selection) clipboard.set_text(url, -1) def url_set(self, button, menuitem): url = self.screen.get_url(self.name) size = 80 if len(url) > size: url = url[:size // 2] + '...' + url[-size // 2:] menuitem.set_label(url) def set_cursor(self): if self.screen: self.screen.set_cursor(reset_view=False) def attach_drag_data_received(self, widget, context, x, y, selection, info, timestamp): record = self.screen.current_record if not record or record.id < 0: return win_attach = Attachment(record, lambda: self.refresh_resources(reload=True)) if info == 0: if selection.get_uris(): for uri in selection.get_uris(): # Win32 cut&paste terminates the list with a NULL character if not uri or uri == '\0': continue win_attach.add_uri(uri) else: win_attach.add_uri(selection.get_text()) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685599.0 tryton-7.0.24/tryton/gui/window/infobar.py0000644000175000017500000000273214517761237016720 0ustar00cedced# This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. from gi.repository import Gtk class InfoBar(object): toolbar = None def create_info_bar(self): self.__box = Gtk.VBox() self.__box.show() self.__messages = set() self.__kinds = {} return self.__box def info_bar_add(self, message, type_=Gtk.MessageType.ERROR, kind=None): if not message: return key = (message, type_) if key not in self.__messages: info_bar = Gtk.InfoBar() self.__box.pack_start(info_bar, False, False, 0) area = info_bar.get_content_area() area.add(Gtk.Label(label=message)) info_bar.set_show_close_button(True) info_bar.connect('response', self.__response, key) info_bar.set_message_type(type_) info_bar.show_all() self.__kinds[info_bar] = kind def __response(self, widget, response, key): self.__messages.add(key) self.__box.remove(widget) def info_bar_refresh(self, kind=None): for child in self.__box.get_children(): if self.__kinds[child] == kind: self.__box.remove(child) del self.__kinds[child] def info_bar_clear(self): for kind in set(self.__kinds.values()): self.info_bar_refresh(kind=kind) self.__messages.clear() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685599.0 tryton-7.0.24/tryton/gui/window/limit.py0000644000175000017500000000506714517761237016422 0ustar00cedced# This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. import gettext import sys from gi.repository import Gtk from tryton.common import IconFactory, get_toplevel_window from tryton.common.underline import set_underline from tryton.config import CONFIG, TRYTON_ICON from tryton.gui import Main _ = gettext.gettext class Limit(object): 'Set Search Limit' def __init__(self): self.parent = get_toplevel_window() self.win = Gtk.Dialog( title=_('Limit'), transient_for=self.parent, modal=True, destroy_with_parent=True) Main().add_window(self.win) cancel_button = self.win.add_button( set_underline(_("Cancel")), Gtk.ResponseType.CANCEL) cancel_button.set_image(IconFactory.get_image( 'tryton-cancel', Gtk.IconSize.BUTTON)) cancel_button.set_always_show_image(True) ok_button = self.win.add_button( set_underline(_("OK")), Gtk.ResponseType.OK) ok_button.set_image(IconFactory.get_image( 'tryton-ok', Gtk.IconSize.BUTTON)) ok_button.set_always_show_image(True) self.win.set_default_response(Gtk.ResponseType.OK) self.win.set_icon(TRYTON_ICON) self.win.vbox.set_spacing(3) self.win.vbox.pack_start(Gtk.Label( label=_('Search Limit Settings')), expand=False, fill=True, padding=0) self.win.vbox.pack_start( Gtk.HSeparator(), expand=True, fill=True, padding=0) hbox = Gtk.HBox(spacing=3) label = Gtk.Label(label=_('Limit:')) hbox.pack_start(label, expand=True, fill=True, padding=0) adjustment = Gtk.Adjustment( value=CONFIG['client.limit'], lower=1, upper=sys.maxsize, step_incr=10, page_incr=100) self.spin_limit = Gtk.SpinButton() self.spin_limit.configure(adjustment, climb_rate=1, digits=0) self.spin_limit.set_numeric(False) self.spin_limit.set_activates_default(True) label.set_mnemonic_widget(self.spin_limit) hbox.pack_start(self.spin_limit, expand=True, fill=True, padding=0) self.win.vbox.pack_start(hbox, expand=True, fill=True, padding=0) self.win.show_all() def run(self): 'Run the window' res = self.win.run() if res == Gtk.ResponseType.OK: CONFIG['client.limit'] = self.spin_limit.get_value_as_int() CONFIG.save() self.parent.present() self.win.destroy() return res ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685599.0 tryton-7.0.24/tryton/gui/window/log.py0000644000175000017500000000707614517761237016067 0ustar00cedced# This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. import gettext from gi.repository import Gtk from tryton.common import RPCException, RPCExecute, timezoned_date from tryton.common.underline import set_underline from tryton.gui.window.view_form.screen import Screen from tryton.gui.window.win_form import WinForm _ = gettext.gettext class Log(WinForm): def __init__(self, record): self.resource = '%s,%s' % (record.model_name, record.id) title = _("Logs (%s)") % record.rec_name() context = record.get_context() try: log, = RPCExecute( 'model', record.model_name, 'read', [record.id], ['create_uid.rec_name', 'create_date', 'write_uid.rec_name', 'write_date'], context=context) except RPCException: return date_format = context.get('date_format', '%x') datetime_format = date_format + ' %H:%M:%S.%f' grid = Gtk.Grid( column_spacing=3, row_spacing=3, border_width=3) entry_model = Gtk.Entry(editable=False) entry_model.set_text(record.model_name) grid.attach(entry_model, 1, 1, 1, 1) label_model = Gtk.Label( label=set_underline(_("Model:")), use_underline=True, halign=Gtk.Align.END) label_model.set_mnemonic_widget(entry_model) grid.attach(label_model, 0, 1, 1, 1) entry_id = Gtk.Entry(editable=False) entry_id.set_alignment(1) entry_id.set_text(str(record.id)) grid.attach(entry_id, 3, 1, 1, 1) label_id = Gtk.Label( label=set_underline(_("ID:")), use_underline=True, halign=Gtk.Align.END) label_id.set_mnemonic_widget(entry_id) grid.attach(label_id, 2, 1, 1, 1) for i, (user, user_label, date, date_label) in enumerate([ ('create_uid.', _("Created by:"), 'create_date', _("Created at:")), ('write_uid.', _("Last Modified by:"), 'write_date', _("Last Modified at:"))], 2): entry_user = Gtk.Entry(editable=False, width_chars=50) user = log.get(user) if user: user = user.get('rec_name', '') entry_user.set_text(user or '') grid.attach(entry_user, 1, i, 1, 1) label_user = Gtk.Label( label=set_underline(user_label), use_underline=True, halign=Gtk.Align.END) label_user.set_mnemonic_widget(entry_user) grid.attach(label_user, 0, i, 1, 1) entry_date = Gtk.Entry(editable=False) date = log.get(date) if date: date = timezoned_date(date).strftime(datetime_format) entry_date.set_width_chars(len(date or '')) entry_date.set_text(date or '') grid.attach(entry_date, 3, i, 1, 1) label_date = Gtk.Label( label=set_underline(date_label), use_underline=True, halign=Gtk.Align.END) label_date.set_mnemonic_widget(entry_date) grid.attach(label_date, 2, i, 1, 1) grid.show_all() screen = Screen('ir.model.log', domain=[ ('resource', '=', self.resource), ], mode=['tree', 'form']) super().__init__(screen, view_type='tree', title=title) screen.search_filter() self.win.vbox.pack_start(grid, expand=False, fill=True, padding=0) self.win.vbox.reorder_child(grid, 2) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685599.0 tryton-7.0.24/tryton/gui/window/nomodal.py0000644000175000017500000000321114517761237016722 0ustar00cedced# This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. import tryton.common as common class NoModal(object): def __init__(self): self.parent = common.get_toplevel_window() self.sensible_widget = common.get_sensible_widget(self.parent) self.page = None self.parent_focus = [] focus = self.parent.get_focus() while focus: self.parent_focus.append(focus) focus = focus.get_parent() def register(self): from tryton.gui.main import Main main = Main() self.page = main.get_page() if not self.page: self.page = main self.page.dialogs.append(self) self.sensible_widget.props.sensitive = False def destroy(self): if not self.page: return self.page.dialogs.remove(self) self.parent.present() self.sensible_widget.props.sensitive = True for focus in self.parent_focus: if focus and focus.is_ancestor(self.parent): try: focus.grab_focus() except TypeError: # GooCanvas needs a GooCanvasItem continue break def show(self): raise NotImplementedError def hide(self): raise NotImplementedError def default_size(self): from tryton.gui.main import Main main = Main() allocation = main.window.get_allocation() width, height = allocation.width, allocation.height return max(width - 150, 0), max(height - 150, 0) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685599.0 tryton-7.0.24/tryton/gui/window/note.py0000644000175000017500000000244714517761237016250 0ustar00cedced# This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. import gettext from tryton.gui.window.view_form.screen import Screen from tryton.gui.window.win_form import WinForm _ = gettext.gettext class Note(WinForm): "Note window" def __init__(self, record, callback=None): self.resource = '%s,%s' % (record.model_name, record.id) self.note_callback = callback title = _('Notes (%s)') % (record.rec_name()) screen = Screen('ir.note', domain=[ ('resource', '=', self.resource), ], mode=['tree', 'form']) super(Note, self).__init__(screen, self.callback, view_type='tree', title=title) screen.search_filter() def destroy(self): self.prev_view.save_width() super(Note, self).destroy() def callback(self, result): if result: unread = self.screen.group.fields['unread'] for record in self.screen.group: if record.loaded or record.id < 0: if 'unread' not in record.modified_fields: unread.set_client(record, False) self.screen.save_current() if self.note_callback: self.note_callback() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685599.0 tryton-7.0.24/tryton/gui/window/preference.py0000644000175000017500000000754014517761237017420 0ustar00cedced# This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. "Preference" import copy import gettext from gi.repository import Gdk, Gtk import tryton.rpc as rpc from tryton.common import IconFactory, RPCException, RPCExecute from tryton.common.underline import set_underline from tryton.config import TRYTON_ICON from tryton.gui import Main from tryton.gui.window.nomodal import NoModal from tryton.gui.window.view_form.screen import Screen _ = gettext.gettext class Preference(NoModal): "Preference window" def __init__(self, user, callback): NoModal.__init__(self) self.callback = callback self.win = Gtk.Dialog( title=_('Preferences'), transient_for=self.parent, destroy_with_parent=True) Main().add_window(self.win) self.win.set_position(Gtk.WindowPosition.CENTER_ON_PARENT) self.win.set_icon(TRYTON_ICON) self.accel_group = Gtk.AccelGroup() self.win.add_accel_group(self.accel_group) self.but_cancel = self.win.add_button( set_underline(_("Cancel")), Gtk.ResponseType.CANCEL) self.but_cancel.set_image( IconFactory.get_image('tryton-cancel', Gtk.IconSize.BUTTON)) self.but_cancel.set_always_show_image(True) self.but_ok = self.win.add_button( set_underline(_("OK")), Gtk.ResponseType.OK) self.but_ok.set_image( IconFactory.get_image('tryton-ok', Gtk.IconSize.BUTTON)) self.but_ok.set_always_show_image(True) self.but_ok.add_accelerator( 'clicked', self.accel_group, Gdk.KEY_Return, Gdk.ModifierType.CONTROL_MASK, Gtk.AccelFlags.VISIBLE) self.win.set_default_response(Gtk.ResponseType.OK) self.win.connect('response', self.response) try: view = RPCExecute('model', 'res.user', 'get_preferences_fields_view') except RPCException: self.win.destroy() self.win = None return title = Gtk.Label(label=_('Edit User Preferences')) title.show() self.win.vbox.pack_start(title, expand=False, fill=True, padding=0) self.screen = Screen('res.user', mode=[]) # Reset readonly set automaticly by MODELACCESS self.screen.readonly = False self.screen.group.readonly = False self.screen.group.skip_model_access = True self.screen.add_view(view) self.screen.switch_view() self.screen.new(default=False) try: preferences = RPCExecute('model', 'res.user', 'get_preferences', False) except RPCException: self.win.destroy() self.win = None return self.screen.current_record.cancel() self.screen.current_record.set(preferences) self.screen.current_record.id = rpc._USER self.screen.current_record.validate(softvalidation=True) self.screen.display(set_cursor=True) self.screen.widget.show() self.win.vbox.pack_start( self.screen.widget, expand=True, fill=True, padding=0) self.win.set_title(_('Preference')) self.win.set_default_size(*self.default_size()) self.register() self.win.show() def response(self, win, response_id): if response_id == Gtk.ResponseType.OK: if self.screen.current_record.validate(): vals = copy.copy(self.screen.get()) try: RPCExecute('model', 'res.user', 'set_preferences', vals) except RPCException: return rpc.context_reset() self.parent.present() self.destroy() self.callback() def destroy(self): self.screen.destroy() self.win.destroy() NoModal.destroy(self) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685599.0 tryton-7.0.24/tryton/gui/window/revision.py0000644000175000017500000001041214517761237017130 0ustar00cedced# This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. import gettext from gi.repository import Gtk from tryton.common import ( IconFactory, get_toplevel_window, timezoned_date, untimezoned_date) from tryton.common.datetime_ import date_parse from tryton.common.underline import set_underline from tryton.config import TRYTON_ICON from tryton.gui import Main _ = gettext.gettext class Revision(object): 'Ask revision' def __init__(self, revisions, revision=None, format_='%x %H:%M:%S.%f'): self.parent = get_toplevel_window() self.win = Gtk.Dialog( title=_('Revision'), transient_for=self.parent, modal=True, destroy_with_parent=True) Main().add_window(self.win) cancel_button = self.win.add_button( set_underline(_("Cancel")), Gtk.ResponseType.CANCEL) cancel_button.set_image(IconFactory.get_image( 'tryton-cancel', Gtk.IconSize.BUTTON)) cancel_button.set_always_show_image(True) ok_button = self.win.add_button( set_underline(_("OK")), Gtk.ResponseType.OK) ok_button.set_image(IconFactory.get_image( 'tryton-ok', Gtk.IconSize.BUTTON)) ok_button.set_always_show_image(True) self.win.set_default_response(Gtk.ResponseType.OK) self.win.set_icon(TRYTON_ICON) self.win.vbox.set_spacing(3) self.win.vbox.pack_start(Gtk.Label( label=_('Select a revision')), expand=False, fill=True, padding=0) self.win.vbox.pack_start( Gtk.HSeparator(), expand=True, fill=True, padding=0) hbox = Gtk.HBox(spacing=3) label = Gtk.Label(label=_('Revision:')) hbox.pack_start(label, expand=True, fill=True, padding=0) list_store = Gtk.ListStore(str, str) # Set model on instantiation to get the default cellrenderer as text combobox = Gtk.ComboBox(model=list_store, has_entry=True) self.entry = combobox.get_child() self.entry.connect('focus-out-event', self.focus_out) self.entry.connect('activate', self.activate) label.set_mnemonic_widget(self.entry) combobox.connect('changed', self.changed) self.entry.set_property('activates_default', True) self._format = format_ if revision: self.entry.set_text(revision.strftime(self._format)) self._value = revision active = -1 else: self._value = None active = 0 list_store.append(('', '')) for i, (rev, id_, name) in enumerate(revisions, 1): list_store.append( (timezoned_date(rev).strftime(self._format), name)) if rev == revision: active = i combobox.set_active(active) cell = Gtk.CellRendererText() combobox.pack_start(cell, expand=True) combobox.add_attribute(cell, 'text', 1) hbox.pack_start(combobox, expand=True, fill=True, padding=0) combobox.set_entry_text_column(0) self.win.vbox.pack_start(hbox, expand=True, fill=True, padding=0) self.win.show_all() def focus_out(self, entry, event): self.parse() self.update() return False def activate(self, entry): self.parse() self.update() return False def changed(self, combobox): # "changed" signal is also triggered by text editing # so only parse when a row is active if combobox.get_active_iter(): self.parse() self.update() return False def parse(self): text = self.entry.get_text() value = None if text: try: value = untimezoned_date(date_parse(text, self._format)) except ValueError: pass self._value = value def update(self): if not self._value: self.entry.set_text('') else: self.entry.set_text(self._value.strftime(self._format)) def run(self): response = self.win.run() revision = None if response == Gtk.ResponseType.OK: revision = self._value self.parent.present() self.win.destroy() return revision ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685599.0 tryton-7.0.24/tryton/gui/window/tabcontent.py0000644000175000017500000002566314517761237017451 0ustar00cedced# This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. import gettext from gi.repository import Gtk, Pango import tryton.common as common from tryton.common.widget_style import widget_class from tryton.config import CONFIG from tryton.gui import Main from .infobar import InfoBar _ = gettext.gettext class ToolbarItem(object): def __init__(self, id, label, tooltip=None, icon_name=None, accel_path=None, toggle=False): self.id = id self.label = label self.tooltip = tooltip self.icon_name = icon_name self.accel_path = accel_path self.toggle = toggle @property def menu(self): return True @property def toolbar(self): return bool(self.tooltip) class TabContent(InfoBar): def __init__(self, **attributes): super(TabContent, self).__init__() self.attributes = attributes.copy() @property def menu_def(self): return [ ToolbarItem( id='switch', label=_("_Switch View"), tooltip=_("Switch View"), icon_name='tryton-switch', accel_path='/Form/Switch View'), ToolbarItem( id='previous', label=_("_Previous"), tooltip=_("Previous Record"), icon_name='tryton-back', accel_path='/Form/Previous'), ToolbarItem( id='next', label=_("_Next"), tooltip=_("Next Record"), icon_name='tryton-forward', accel_path='/Form/Next'), ToolbarItem( id='search', label=_("_Search"), icon_name='tryton-search', accel_path='/Form/Search'), None, ToolbarItem( id='new', label=_("_New"), tooltip=_("Create a new record"), icon_name='tryton-create', accel_path='/Form/New'), ToolbarItem( id='save', label=_("_Save"), tooltip=_("Save this record"), icon_name='tryton-save', accel_path='/Form/Save'), ToolbarItem( id='reload', label=_("_Reload/Undo"), tooltip=_("Reload/Undo"), icon_name='tryton-refresh', accel_path='/Form/Reload'), ToolbarItem( id='copy', label=_("_Duplicate"), icon_name='tryton-copy', accel_path='/Form/Duplicate'), ToolbarItem( id='remove', label=_("_Delete..."), icon_name='tryton-delete', accel_path='/Form/Delete'), None, ToolbarItem( id='logs', label=_("View _Logs..."), icon_name='tryton-log'), ToolbarItem( id='revision' if self.model in common.MODELHISTORY else None, label=_("Show revisions..."), icon_name='tryton-history'), None, ToolbarItem( id='attach', label=_("A_ttachments..."), tooltip=_("Add an attachment to the record"), icon_name='tryton-attach', accel_path='/Form/Attachments', toggle=True), ToolbarItem( id='note', label=_("_Notes..."), tooltip=_("Add a note to the record"), icon_name='tryton-note', accel_path='/Form/Notes'), ToolbarItem( id='action', label=_("_Actions..."), icon_name='tryton-launch', accel_path='/Form/Actions'), ToolbarItem( id='relate', label=_("_Relate..."), icon_name='tryton-link', accel_path='/Form/Relate'), None, ToolbarItem( id='print_open', label=_("_Report..."), icon_name='tryton-open', accel_path='/Form/Report'), ToolbarItem( id='print', label=_("_Print..."), icon_name='tryton-print', accel_path='/Form/Print'), ToolbarItem( id='email', label=_("_E-Mail..."), tooltip=_("Send an e-mail using the record"), icon_name='tryton-email', accel_path='/Form/Email'), None, ToolbarItem( id='export', label=_("_Export Data..."), icon_name='tryton-export', accel_path='/Form/Export Data'), ToolbarItem( id='import', label=_("_Import Data..."), icon_name='tryton-import', accel_path='/Form/Import Data'), ToolbarItem( id='copy_url', label=_("Copy _URL..."), icon_name='tryton-public', accel_path='/Form/Copy URL'), None, ToolbarItem( id='win_close', label=_("_Close Tab"), icon_name='tryton-close', accel_path='/Form/Close'), ] def create_tabcontent(self): self.buttons = {} self.menu_buttons = {} self.tooltips = common.Tooltips() self.accel_group = Main().accel_group self.widget = Gtk.VBox(spacing=3) self.widget.show() title_box = self.make_title_bar() self.widget.pack_start(title_box, expand=False, fill=True, padding=3) self.toolbar = self.create_toolbar(self.get_toolbars()) self.toolbar.show_all() self.widget.pack_start( self.toolbar, expand=False, fill=True, padding=0) self.main = Gtk.HPaned() self.main.show() self.widget.pack_start( self.main, expand=True, fill=True, padding=0) viewport = Gtk.Viewport() viewport.set_shadow_type(Gtk.ShadowType.NONE) viewport.add(self.widget_get()) viewport.show() self.scrolledwindow = Gtk.ScrolledWindow() self.scrolledwindow.set_shadow_type(Gtk.ShadowType.NONE) self.scrolledwindow.set_policy( Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC) self.scrolledwindow.add(viewport) self.scrolledwindow.show() self.main.pack1(self.scrolledwindow, resize=True, shrink=False) self.widget.pack_start( self.create_info_bar(), expand=False, fill=True, padding=0) def make_title_bar(self): tooltips = common.Tooltips() self.title = title = Gtk.Label( label=common.ellipsize(self.name, 80), halign=Gtk.Align.START, margin=5, ellipsize=Pango.EllipsizeMode.END) tooltips.set_tip(title, self.name) title.set_size_request(0, -1) # Allow overflow title.show() menu = Gtk.MenuButton.new() menu.set_relief(Gtk.ReliefStyle.NONE) menu.set_popup(self.set_menu_form()) menu.show() self.status_label = Gtk.Label( margin=5, halign=Gtk.Align.END) widget_class(self.status_label, 'status', True) self.status_label.show() hbox = Gtk.HBox() hbox.pack_start(title, expand=True, fill=True, padding=0) hbox.pack_start(self.status_label, expand=False, fill=True, padding=0) hbox.show() frame = Gtk.Frame() frame.set_shadow_type(Gtk.ShadowType.ETCHED_IN) widget_class(frame, 'window-title', True) frame.add(hbox) frame.show() frame_menu = Gtk.Frame() frame_menu.set_shadow_type(Gtk.ShadowType.ETCHED_IN) frame_menu.add(menu) frame_menu.show() title_box = Gtk.HBox() title_box.pack_start(frame_menu, expand=False, fill=True, padding=0) title_box.pack_start(frame, expand=True, fill=True, padding=0) title_box.show() return title_box def create_base_toolbar(self, toolbar): previous = None for item in self.menu_def: if item and item.toolbar: callback = getattr(self, 'sig_%s' % item.id, None) if not callback: continue if item.toggle: toolitem = Gtk.ToggleToolButton() toolitem.connect('toggled', callback) else: toolitem = Gtk.ToolButton() toolitem.connect('clicked', callback) toolitem.set_icon_widget( common.IconFactory.get_image( item.icon_name, Gtk.IconSize.LARGE_TOOLBAR)) toolitem.set_label(item.label) toolitem.set_use_underline(True) self.tooltips.set_tip(toolitem, item.tooltip) self.buttons[item.id] = toolitem elif not item and previous: toolitem = Gtk.SeparatorToolItem() else: continue previous = item toolbar.insert(toolitem, -1) def set_menu_form(self): menu_form = Gtk.Menu() menu_form.set_accel_group(self.accel_group) menu_form.set_accel_path('/Form') previous = None for item in self.menu_def: if item and item.menu: callback = getattr(self, 'sig_%s' % item.id, None) if not callback: continue menuitem = Gtk.MenuItem( label=item.label, use_underline=True) menuitem.connect('activate', callback) if item.accel_path: menuitem.set_accel_path(item.accel_path) self.menu_buttons[item.id] = menuitem elif not item and previous: menuitem = Gtk.SeparatorMenuItem() else: continue previous = item menu_form.add(menuitem) menu_form.show_all() return menu_form def create_toolbar(self, toolbars): gtktoolbar = Gtk.Toolbar() option = CONFIG['client.toolbar'] if option == 'default': gtktoolbar.set_style(False) elif option == 'both': gtktoolbar.set_style(Gtk.ToolbarStyle.BOTH) elif option == 'text': gtktoolbar.set_style(Gtk.ToolbarStyle.TEXT) elif option == 'icons': gtktoolbar.set_style(Gtk.ToolbarStyle.ICONS) self.create_base_toolbar(gtktoolbar) return gtktoolbar def compare(self, model, attributes): return False ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1745680285.210778 tryton-7.0.24/tryton/gui/window/view_board/0000755000175000017500000000000015003173635017031 5ustar00cedced././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685599.0 tryton-7.0.24/tryton/gui/window/view_board/__init__.py0000644000175000017500000000031114517761237021147 0ustar00cedced# This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. from .view_board import ViewBoard __all__ = [ViewBoard] ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685599.0 tryton-7.0.24/tryton/gui/window/view_board/action.py0000644000175000017500000001257614517761237020705 0ustar00cedced# This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. 'Action' import gettext from gi.repository import GLib, Gtk import tryton.rpc as rpc from tryton.action import Action as GenericAction from tryton.common import RPCException, RPCExecute from tryton.config import CONFIG from tryton.gui.window.view_form.screen import Screen from tryton.gui.window.win_form import WinForm from tryton.pyson import PYSONDecoder _ = gettext.gettext class Action: def __init__(self, view, attrs=None): super(Action, self).__init__() self.name = attrs['name'] self.view = view try: self.action = RPCExecute('model', 'ir.action.act_window', 'get', self.name) except RPCException: raise view_ids = [] view_mode = None if self.action.get('views', []): view_ids = [x[0] for x in self.action['views']] view_mode = [x[1] for x in self.action['views']] elif self.action.get('view_id', False): view_ids = [self.action['view_id'][0]] self.action.setdefault('pyson_domain', '[]') ctx = {} ctx.update(rpc.CONTEXT) ctx['_user'] = rpc._USER decoder = PYSONDecoder(ctx) action_ctx = self.view.context.copy() action_ctx.update( decoder.decode(self.action.get('pyson_context') or '{}')) ctx.update(action_ctx) ctx['context'] = ctx decoder = PYSONDecoder(ctx) order = decoder.decode(self.action['pyson_order']) search_value = decoder.decode( self.action['pyson_search_value'] or '[]') tab_domain = [(n, decoder.decode(d), c) for n, d, c in self.action['domains']] limit = self.action.get('limit') if limit is None: limit = CONFIG['client.limit'] self.context = ctx self.domain = [] self.update_domain([]) self.widget = Gtk.Frame() self.widget.set_border_width(0) vbox = Gtk.VBox(homogeneous=False, spacing=3) self.widget.add(vbox) self.title = Gtk.Label() self.widget.set_label_widget(self.title) self.widget.set_label_align(0.0, 0.5) self.widget.show_all() self.screen = Screen(self.action['res_model'], view_ids=view_ids, domain=self.domain, context=action_ctx, order=order, mode=view_mode, limit=limit, search_value=search_value, tab_domain=tab_domain, context_model=self.action['context_model'], context_domain=self.action['context_domain'], row_activate=self.row_activate) self.screen.windows.append(self) vbox.pack_start( self.screen.widget, expand=True, fill=True, padding=0) if attrs.get('string'): self.title.set_text(attrs['string']) else: self.title.set_text(self.action['name']) self.widget.set_size_request(int(attrs.get('width', -1)), int(attrs.get('height', -1))) self.screen.search_filter() def row_activate(self): if not self.screen.current_record: return if (self.screen.current_view.view_type == 'tree' and int(self.screen.current_view.attributes.get( 'keyword_open', 0))): GenericAction.exec_keyword('tree_open', { 'model': self.screen.model_name, 'id': (self.screen.current_record.id if self.screen.current_record else None), 'ids': [r.id for r in self.screen.selected_records], }, context=self.screen.group._context.copy(), warning=False) else: def callback(result): if result: self.screen.current_record.save() else: self.screen.current_record.cancel() WinForm(self.screen, callback, title=self.title.get_text()) def display(self): self.screen.search_filter(self.screen.screen_container.get_text()) def record_message(self, *args): self.view.active_changed(self) def _get_active(self): if self.screen and self.screen.current_record: return { 'active_id': self.screen.current_record.id, 'active_ids': [r.id for r in self.screen.selected_records] } else: return { 'active_id': None, 'active_ids': [], } active = property(_get_active) def update_domain(self, actions): domain_ctx = self.context.copy() domain_ctx['_actions'] = {} for action in actions: domain_ctx['_actions'][action.name] = action.active new_domain = PYSONDecoder(domain_ctx).decode( self.action['pyson_domain']) if self.domain == new_domain: return del self.domain[:] self.domain.extend(new_domain) if hasattr(self, 'screen'): # Catch early update # Using idle_add to prevent corruption of the event who triggered # the update. def display(): if self.screen.widget.props.window: self.display() GLib.idle_add(display) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685599.0 tryton-7.0.24/tryton/gui/window/view_board/view_board.py0000644000175000017500000000342214517761237021537 0ustar00cedced# This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. import gettext from tryton.common import node_attributes from tryton.gui.window.view_form.view.form import Container, FormXMLViewParser from .action import Action _ = gettext.gettext class BoardXMLViewParser(FormXMLViewParser): def _parse_board(self, node, attributes): container_attributes = node_attributes(node) container = Container.constructor( int(container_attributes.get('col', 4)), container_attributes.get('homogeneous', False)) self.view.widget = container.container self.parse_child(node, container) assert not self._containers def _parse_action(self, node, attributes): attributes.setdefault('yexpand', True) attributes.setdefault('yfill', True) action = Action(self.view, attributes) self.view.actions.append(action) self.container.add(action.widget, attributes) class ViewBoard(object): 'View board' widget = None xml_parser = BoardXMLViewParser def __init__(self, xml, context=None): self.context = context self.actions = [] self.state_widgets = [] self.xml_parser(self, None, {}).parse(xml) self.widget.show_all() self.active_changed(None) def widget_get(self): return self.widget def reload(self): for action in self.actions: action.display() for state_widget in self.state_widgets: state_widget.state_set(None) def active_changed(self, event_action): for action in self.actions: if action == event_action: continue action.update_domain(self.actions) ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1745680285.210778 tryton-7.0.24/tryton/gui/window/view_form/0000755000175000017500000000000015003173635016705 5ustar00cedced././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685599.0 tryton-7.0.24/tryton/gui/window/view_form/__init__.py0000644000175000017500000000027114517761237021030 0ustar00cedced# This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. from . import screen __all__ = [screen] ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1745680285.210778 tryton-7.0.24/tryton/gui/window/view_form/model/0000755000175000017500000000000015003173635020005 5ustar00cedced././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685599.0 tryton-7.0.24/tryton/gui/window/view_form/model/__init__.py0000644000175000017500000000022014517761237022122 0ustar00cedced# This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1735580133.0 tryton-7.0.24/tryton/gui/window/view_form/model/field.py0000644000175000017500000012742314734554745021472 0ustar00cedced# This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. import datetime import decimal import locale import logging import math import os import tempfile from decimal import Decimal from itertools import chain from pathlib import Path import tryton.common as common from tryton.common import ( EvalEnvironment, RPCException, RPCExecute, concat, domain_inversion, eval_domain, extract_reference_models, filter_leaf, inverse_leaf, localize_domain, merge, prepare_reference_domain, simplify, unique_value) from tryton.common.htmltextbuffer import guess_decode from tryton.config import CONFIG from tryton.pyson import PYSONDecoder logger = logging.getLogger(__name__) class Field(object): ''' get: return the values to write to the server get_client: return the value for the client widget (form_gtk) set: save the value from the server set_client: save the value from the widget ''' _default = None _single_value = True @staticmethod def get_field(ctype): return TYPES.get(ctype, CharField) def __init__(self, attrs): self.attrs = attrs self.name = attrs['name'] self.views = set() def sig_changed(self, record): record.on_change([self.name]) record.on_change_with([self.name]) record.autocomplete_with(self.name) record.set_field_context() def domains_get(self, record, pre_validate=None): screen_domain = domain_inversion( [record.group.domain4inversion, pre_validate or []], self.name, EvalEnvironment(record)) if isinstance(screen_domain, bool) and not screen_domain: screen_domain = [('id', '=', None)] elif isinstance(screen_domain, bool) and screen_domain: screen_domain = [] attr_domain = record.expr_eval(self.attrs.get('domain', [])) return screen_domain, attr_domain def domain_get(self, record): screen_domain, attr_domain = self.domains_get(record) return concat(localize_domain(screen_domain), attr_domain) def validation_domains(self, record, pre_validate=None): return concat(*self.domains_get(record, pre_validate)) def get_context(self, record, record_context=None, local=False): if record_context is not None: context = record_context.copy() else: context = record.get_context(local=local) context.update(record.expr_eval(self.attrs.get('context', {}))) return context def get_search_context(self, record): context = self.get_context(record) context.update(record.expr_eval(self.attrs.get('search_context', {}))) return context def get_search_order(self, record): order = record.expr_eval(self.attrs.get('search_order', None)) return order def _is_empty(self, record): return not self.get_eval(record) def check_required(self, record): state_attrs = self.get_state_attrs(record) if bool(int(state_attrs.get('required') or 0)): if (self._is_empty(record) and not bool(int(state_attrs.get('readonly') or 0))): return False return True def validate(self, record, softvalidation=False, pre_validate=None): if self.attrs.get('readonly'): return True state_attrs = self.get_state_attrs(record) is_required = bool(int(state_attrs.get('required') or 0)) is_invisible = bool(int(state_attrs.get('invisible') or 0)) invalid = False state_attrs['domain_readonly'] = False domain = simplify(self.validation_domains(record, pre_validate)) if not softvalidation: if not self.check_required(record): invalid = 'required' if isinstance(domain, bool): if not domain: invalid = 'domain' elif domain == [('id', '=', None)]: invalid = 'domain' else: screen_domain, _ = self.domains_get(record, pre_validate) unique, leftpart, value = unique_value( domain, single_value=self._single_value) unique_from_screen, _, _ = unique_value( screen_domain, single_value=self._single_value) if (self._is_empty(record) and not is_required and not is_invisible and not unique_from_screen): pass elif unique: # If the inverted domain is so constraint that only one value # is possible we should use it. But we must also pay attention # to the fact that the original domain might be a 'OR' domain # and thus not preventing the modification of fields. if value is False: # XXX to remove once server domains are fixed value = None setdefault = True if record.group.domain: original_domain = merge(record.group.domain) else: original_domain = merge(domain) domain_readonly = original_domain[0] == 'AND' if '.' in leftpart: recordpart, localpart = leftpart.split('.', 1) constraintfields = set() if domain_readonly: for leaf in localize_domain(original_domain[1:]): constraintfields.add(leaf[0]) if localpart != 'id' or recordpart not in constraintfields: setdefault = False if setdefault and not pre_validate: self.set_client(record, value) state_attrs['domain_readonly'] = domain_readonly if not eval_domain(domain, EvalEnvironment(record)): invalid = domain state_attrs['invalid'] = invalid return not invalid def set(self, record, value): record.value[self.name] = value def get(self, record): return record.value.get(self.name, self._default) def get_eval(self, record): return self.get(record) def get_on_change_value(self, record): return self.get_eval(record) def set_client(self, record, value, force_change=False): previous_value = self.get(record) self.set(record, value) if previous_value != self.get(record): self.sig_changed(record) record.validate(softvalidation=True) record.set_modified(self.name) elif force_change: self.sig_changed(record) record.validate(softvalidation=True) record.set_modified() def get_client(self, record): return self.get(record) def set_default(self, record, value): self.set(record, value) record.modified_fields.setdefault(self.name) def set_on_change(self, record, value): record.modified_fields.setdefault(self.name) self.set(record, value) def state_set(self, record, states=('readonly', 'required', 'invisible')): state_changes = record.expr_eval(self.attrs.get('states', {})) for key in states: if key == 'readonly' and self.attrs.get(key, False): continue if key in state_changes: self.get_state_attrs(record)[key] = state_changes[key] elif key in self.attrs: self.get_state_attrs(record)[key] = self.attrs[key] if (record.group.readonly or self.get_state_attrs(record).get('domain_readonly') or record.parent_name == self.name): self.get_state_attrs(record)['readonly'] = True def get_state_attrs(self, record): if self.name not in record.state_attrs: record.state_attrs[self.name] = self.attrs.copy() if record.group.readonly or record.readonly: record.state_attrs[self.name]['readonly'] = True return record.state_attrs[self.name] def get_timestamp(self, record): return {} class CharField(Field): _default = '' def set(self, record, value): if self.attrs.get('strip') and value: if self.attrs['strip'] == 'leading': value = value.lstrip() elif self.attrs['strip'] == 'trailing': value = value.rstrip() else: value = value.strip() super().set(record, value) def get(self, record): return super(CharField, self).get(record) or self._default def set_client(self, record, value, force_change=False): if isinstance(value, bytes): try: value = guess_decode(value) except UnicodeDecodeError: logger.warning( "The encoding can not be guessed for field '%(name)s'", {'name': self.name}) value = None super().set_client(record, value, force_change) class SelectionField(Field): _default = None def set_client(self, record, value, force_change=False): record.value.pop(self.name + ':string', None) super().set_client(record, value, force_change=force_change) class MultiSelectionField(SelectionField): _default = None _single_value = False def get(self, record): value = super().get(record) if not value: value = self._default else: value.sort() return value def get_eval(self, record): value = super().get_eval(record) if value is None: value = [] return value def set_client(self, record, value, force_change=False): if value is None: value = [] if isinstance(value, str): value = [value] if value: value = sorted(value) super().set_client(record, value, force_change=force_change) class DateTimeField(Field): _default = None def set_client(self, record, value, force_change=False): if isinstance(value, datetime.time): current_value = self.get_client(record) if current_value: value = datetime.datetime.combine( current_value.date(), value) else: value = None elif value and not isinstance(value, datetime.datetime): current_value = self.get_client(record) if current_value: time = current_value.time() else: time = datetime.time() value = datetime.datetime.combine(value, time) if value: value = common.untimezoned_date(value) super(DateTimeField, self).set_client(record, value, force_change=force_change) def get_client(self, record): value = super(DateTimeField, self).get_client(record) if value: return common.timezoned_date(value) def date_format(self, record): context = self.get_context(record) return common.date_format(context.get('date_format')) def time_format(self, record): return record.expr_eval(self.attrs['format']) class DateField(Field): _default = None def set_client(self, record, value, force_change=False): if isinstance(value, datetime.datetime): assert value.time() == datetime.time() value = value.date() super(DateField, self).set_client(record, value, force_change=force_change) def date_format(self, record): context = self.get_context(record) return common.date_format(context.get('date_format')) class TimeField(Field): _default = None def _is_empty(self, record): return self.get(record) is None def set_client(self, record, value, force_change=False): if isinstance(value, datetime.datetime): value = value.time() super(TimeField, self).set_client(record, value, force_change=force_change) def time_format(self, record): return record.expr_eval(self.attrs['format']) class TimeDeltaField(Field): _default = None def _is_empty(self, record): return self.get(record) is None def converter(self, group): return group.context.get(self.attrs.get('converter')) def set_client(self, record, value, force_change=False): if isinstance(value, str): value = common.timedelta.parse(value, self.converter(record.group)) super(TimeDeltaField, self).set_client( record, value, force_change=force_change) def get_client(self, record): value = super(TimeDeltaField, self).get_client(record) return common.timedelta.format(value, self.converter(record.group)) class FloatField(Field): _default = None def __init__(self, attrs): super().__init__(attrs) self._digits = {} self._symbol = {} def _is_empty(self, record): return self.get(record) is None def get(self, record): return record.value.get(self.name, self._default) def digits(self, record, factor=1): digits = record.expr_eval(self.attrs.get('digits')) if isinstance(digits, str): if digits not in record.group.fields: return digits_field = record.group.fields[digits] digits_name = digits_field.attrs.get('relation') digits_id = digits_field.get(record) if digits_name and digits_id is not None and digits_id >= 0: if digits_id in self._digits: digits = self._digits[digits_id] else: try: digits = RPCExecute( 'model', digits_name, 'get_digits', digits_id) except RPCException: logger.warn( "Fail to fetch digits for %s,%s", digits_name, digits_id) return self._digits[digits_id] = digits else: return if not digits or any(d is None for d in digits): return shift = int(round(math.log(abs(factor), 10))) return (digits[0] + shift, digits[1] - shift) def get_symbol(self, record, symbol): if record and symbol in record.group.fields: value = self.get(record) or 0 sign = 1 if value < 0: sign = -1 elif value == 0: sign = 0 symbol_field = record.group.fields[symbol] symbol_name = symbol_field.attrs.get('relation') symbol_id = symbol_field.get(record) if symbol_name and symbol_id is not None and symbol_id >= 0: if symbol_id in self._symbol: return self._symbol[symbol_id] try: result = RPCExecute( 'model', symbol_name, 'get_symbol', symbol_id, sign, context=record.get_context()) self._symbol[symbol_id] = result return result except RPCException: logger.warn( "Fail to fetch symbol for %s,%s", symbol_name, symbol_id) return '', 1 def convert(self, value): if value is None: return None if isinstance(value, str): value = locale.delocalize( value, self.attrs.get('monetary', False)) try: return self._convert(value) except ValueError: return self._default _convert = float def apply_factor(self, record, value, factor): if value is not None: value /= factor digits = self.digits(record) if digits: value = round(value, digits[1]) value = self.convert(value) return value def set_client(self, record, value, force_change=False, factor=1): value = self.apply_factor(record, self.convert(value), factor) super(FloatField, self).set_client(record, value, force_change=force_change) def get_client(self, record, factor=1, grouping=True): value = record.value.get(self.name) if value is not None: digits = self.digits(record, factor=factor) d = value * factor if not isinstance(d, Decimal): d = Decimal(repr(d)) if digits: p = int(digits[1]) elif d == d.to_integral_value(): p = 0 else: p = -int(d.as_tuple().exponent) monetary = self.attrs.get('monetary', False) return locale.localize( '{0:.{1}f}'.format(d, p), grouping, monetary) else: return '' class NumericField(FloatField): def convert(self, value): try: return super().convert(value) except decimal.InvalidOperation: return self._default _convert = Decimal def set_client(self, record, value, force_change=False, factor=1): return super(NumericField, self).set_client(record, value, force_change=force_change, factor=Decimal(str(factor))) def get_client(self, record, factor=1, grouping=True): return super(NumericField, self).get_client(record, factor=Decimal(str(factor)), grouping=grouping) class IntegerField(FloatField): _convert = int def set_client(self, record, value, force_change=False, factor=1): return super(IntegerField, self).set_client(record, value, force_change=force_change, factor=int(factor)) def get_client(self, record, factor=1, grouping=True): return super(IntegerField, self).get_client( record, factor=int(factor), grouping=grouping) class BooleanField(Field): _default = False def set_client(self, record, value, force_change=False): value = bool(value) super(BooleanField, self).set_client(record, value, force_change=force_change) def get(self, record): return bool(record.value.get(self.name)) def get_client(self, record): return bool(record.value.get(self.name)) class M2OField(Field): ''' internal = (id, name) ''' _default = None def _is_empty(self, record): return self.get(record) is None def get_client(self, record): rec_name = record.value.get(self.name + '.', {}).get('rec_name') if rec_name is None: self.set(record, self.get(record)) rec_name = record.value.get(self.name + '.', {}).get('rec_name') return rec_name or '' def set_client(self, record, value, force_change=False): if isinstance(value, (tuple, list)): value, rec_name = value else: if value == self.get(record): rec_name = record.value.get( self.name + '.', {}).get('rec_name', '') else: rec_name = '' if value and value < 0 and self.name != record.parent_name: value, rec_name = None, '' record.value.setdefault(self.name + '.', {})['rec_name'] = rec_name super(M2OField, self).set_client(record, value, force_change=force_change) def set(self, record, value): rec_name = record.value.get(self.name + '.', {}).get('rec_name') or '' if not rec_name and value is not None and value >= 0: try: result, = RPCExecute('model', self.attrs['relation'], 'read', [value], ['rec_name']) except RPCException: return rec_name = result['rec_name'] or '' record.value.setdefault(self.name + '.', {})['rec_name'] = rec_name record.value[self.name] = value def get_context(self, record, record_context=None, local=False): context = super(M2OField, self).get_context( record, record_context=record_context, local=local) if self.attrs.get('datetime_field'): context['_datetime'] = record.get_eval( )[self.attrs.get('datetime_field')] return context def validation_domains(self, record, pre_validate=None): screen_domain, attr_domain = self.domains_get(record, pre_validate) return screen_domain def domain_get(self, record): screen_domain, attr_domain = self.domains_get(record) return concat(localize_domain(inverse_leaf(screen_domain), self.name), attr_domain) def get_on_change_value(self, record): if record.parent_name == self.name and record.parent: return record.parent.get_on_change_value( skip={record.group.child_name}) return super(M2OField, self).get_on_change_value(record) class O2OField(M2OField): pass class O2MField(Field): ''' internal = Group of the related objects ''' _default = None _single_value = False def __init__(self, attrs): super(O2MField, self).__init__(attrs) def _set_default_value(self, record, fields=None): if record.value.get(self.name) is not None: return from .group import Group parent_name = self.attrs.get('relation_field', '') fields = fields or {} group = Group(self.attrs['relation'], fields, parent=record, parent_name=parent_name, child_name=self.name, parent_datetime_field=self.attrs.get('datetime_field')) if not fields and record.model_name == self.attrs['relation']: group.fields = record.group.fields record.value[self.name] = group def get_client(self, record): self._set_default_value(record) return record.value.get(self.name) def get(self, record): if record.value.get(self.name) is None: return [] record_removed = record.value[self.name].record_removed record_deleted = record.value[self.name].record_deleted result = [] parent_name = self.attrs.get('relation_field', '') to_add = [] to_create = [] to_write = [] for record2 in record.value[self.name]: if record2 in record_removed or record2 in record_deleted: continue if record2.id >= 0: if record2.modified: values = record2.get() values.pop(parent_name, None) if values: to_write.extend(([record2.id], values)) to_add.append(record2.id) else: values = record2.get() values.pop(parent_name, None) to_create.append(values) if to_add: result.append(('add', to_add)) if to_create: result.append(('create', to_create)) if to_write: result.append(('write',) + tuple(to_write)) if record_removed: result.append(('remove', [x.id for x in record_removed])) if record_deleted: result.append(('delete', [x.id for x in record_deleted])) return result def get_timestamp(self, record): if record.value.get(self.name) is None: return {} result = {} record_modified = (r for r in record.value[self.name] if r.modified) for record2 in chain(record_modified, record.value[self.name].record_removed, record.value[self.name].record_deleted): result.update(record2.get_timestamp()) return result def get_eval(self, record): if record.value.get(self.name) is None: return [] record_removed = record.value[self.name].record_removed record_deleted = record.value[self.name].record_deleted return [x.id for x in record.value[self.name] if x not in record_removed and x not in record_deleted] def get_on_change_value(self, record): result = [] if record.value.get(self.name) is None: return [] for record2 in record.value[self.name]: if not (record2.deleted or record2.removed): result.append( record2.get_on_change_value( skip={self.attrs.get('relation_field', '')})) return result def _set_value(self, record, value, default=False, modified=False): self._set_default_value(record) group = record.value[self.name] if value is None: value = [] if not value or isinstance(value[0], int): mode = 'list ids' else: mode = 'list values' if mode == 'list values': context = self.get_context(record) field_names = set(f for v in value for f in v if f not in group.fields and '.' not in f) if field_names: try: fields = RPCExecute('model', self.attrs['relation'], 'fields_get', list(field_names), context=context) except RPCException: return group.load_fields(fields) if mode == 'list ids': records_to_remove = [r for r in group if r.id not in value] for record_to_remove in records_to_remove: group.remove(record_to_remove, remove=True, modified=False) group.load(value, modified=modified or default) else: for vals in value: if 'id' in vals: new_record = group.get(vals['id']) if not new_record: new_record = group.new( default=False, obj_id=vals['id']) else: new_record = group.new(default=False) if default: # Don't validate as parent will validate new_record.set_default( vals, modified=False, validate=False) group.add(new_record, modified=False) else: new_record.set(vals, modified=False) group.append(new_record) # Trigger modified only once group.record_modified() def set(self, record, value, _default=False): group = record.value.get(self.name) fields = {} if group is not None: fields = group.fields.copy() # Unconnect to prevent infinite loop group.parent = None group.destroy() elif record.model_name == self.attrs['relation']: fields = record.group.fields if fields: fields = dict((fname, field.attrs) for fname, field in fields.items()) record.value[self.name] = None self._set_default_value(record, fields=fields) group = record.value[self.name] # Prevent to trigger group-cleared group.parent = None self._set_value(record, value, default=_default) group.parent = record def set_client(self, record, value, force_change=False): # domain inversion could try to set None as value if value is None: value = [] # domain inversion could try to set id as value if isinstance(value, int): value = [value] previous_ids = self.get_eval(record) # The order of the ids is not significant modified = set(previous_ids) != set(value) self._set_value(record, value, modified=modified) if modified: self.sig_changed(record) record.validate(softvalidation=True) record.set_modified(self.name) elif force_change: self.sig_changed(record) record.validate(softvalidation=True) record.set_modified() def set_default(self, record, value): self.set(record, value, _default=True) record.modified_fields.setdefault(self.name) def set_on_change(self, record, value): record.modified_fields.setdefault(self.name) self._set_default_value(record) if isinstance(value, (list, tuple)): self._set_value(record, value, modified=True) return if value and (value.get('add') or value.get('update')): context = self.get_context(record) fields = record.value[self.name].fields values = chain(value.get('update', []), (d for _, d in value.get('add', []))) field_names = set(f for v in values for f in v if f not in fields and f != 'id' and '.' not in f) if field_names: try: new_fields = RPCExecute('model', self.attrs['relation'], 'fields_get', list(field_names), context=context) except RPCException: return else: new_fields = {} group = record.value[self.name] if value and value.get('delete'): for record_id in value['delete']: record2 = group.get(record_id) if record2 is not None: group.remove( record2, remove=False, modified=False, force_remove=False) if value and value.get('remove'): for record_id in value['remove']: record2 = group.get(record_id) if record2 is not None: group.remove( record2, remove=True, modified=False, force_remove=False) if value and (value.get('add') or value.get('update', [])): # First set already added fields to prevent triggering a # second on_change call for vals in value.get('update', []): if 'id' not in vals: continue record2 = group.get(vals['id']) if record2 is not None: vals_to_set = { k: v for k, v in vals.items() if k not in new_fields} record2.set_on_change(vals_to_set) record.value[self.name].add_fields(new_fields) for index, vals in value.get('add', []): new_record = None id_ = vals.pop('id', None) if id_ is not None: new_record = group.get(id_) if not new_record: new_record = group.new(obj_id=id_, default=False) group.add(new_record, index, modified=False) new_record.set_on_change(vals) for vals in value.get('update', []): if 'id' not in vals: continue record2 = group.get(vals['id']) if record2 is not None: record2.set_on_change(vals) def validation_domains(self, record, pre_validate=None): screen_domain, attr_domain = self.domains_get(record, pre_validate) return screen_domain def validate(self, record, softvalidation=False, pre_validate=None): invalid = False ldomain = localize_domain(domain_inversion( record.group.clean4inversion(pre_validate or []), self.name, EvalEnvironment(record)), self.name) if isinstance(ldomain, bool): if ldomain: ldomain = [] else: ldomain = [('id', '=', None)] for record2 in record.value.get(self.name, []): if not record2.loaded and record2.id >= 0 and not pre_validate: continue if not record2.validate(softvalidation=softvalidation, pre_validate=ldomain): invalid = 'children' test = super(O2MField, self).validate(record, softvalidation, pre_validate) if test and invalid: self.get_state_attrs(record)['invalid'] = invalid return False return test def state_set(self, record, states=('readonly', 'required', 'invisible')): self._set_default_value(record) super(O2MField, self).state_set(record, states=states) def get_removed_ids(self, record): return [x.id for x in record.value[self.name].record_removed] def domain_get(self, record): screen_domain, attr_domain = self.domains_get(record) # Forget screen_domain because it only means at least one record # and not all records return attr_domain class M2MField(O2MField): def get_on_change_value(self, record): return self.get_eval(record) class ReferenceField(Field): _default = None def _is_empty(self, record): result = super(ReferenceField, self)._is_empty(record) if not result and (record.value[self.name] is None or record.value[self.name][1] < 0): result = True return result def get_client(self, record): if record.value.get(self.name): model, _ = record.value[self.name] name = record.value.get(self.name + '.', {}).get('rec_name') or '' return model, name else: return None def get(self, record): if (record.value.get(self.name) and record.value[self.name][0] and record.value[self.name][1] is not None and record.value[self.name][1] >= -1): return ','.join(map(str, record.value[self.name])) return None def set_client(self, record, value, force_change=False): if value: if isinstance(value, str): value = value.split(',') ref_model, ref_id = value if isinstance(ref_id, (tuple, list)): ref_id, rec_name = ref_id else: if ref_id: try: ref_id = int(ref_id) except ValueError: pass if '%s,%s' % (ref_model, ref_id) == self.get(record): rec_name = record.value.get( self.name + '.', {}).get('rec_name') or '' else: rec_name = '' record.value.setdefault(self.name + '.', {})['rec_name'] = rec_name value = (ref_model, ref_id) super(ReferenceField, self).set_client(record, value, force_change=force_change) def set(self, record, value): if not value: record.value[self.name] = self._default return if isinstance(value, str): ref_model, ref_id = value.split(',') if not ref_id: ref_id = None else: try: ref_id = int(ref_id) except ValueError: pass else: ref_model, ref_id = value rec_name = record.value.get(self.name + '.', {}).get('rec_name') or '' if ref_model and ref_id is not None and ref_id >= 0: if not rec_name and ref_id >= 0: try: result, = RPCExecute('model', ref_model, 'read', [ref_id], ['rec_name']) except RPCException: return rec_name = result['rec_name'] elif ref_model: rec_name = '' else: rec_name = str(ref_id) if ref_id is not None else '' record.value[self.name] = ref_model, ref_id record.value.setdefault(self.name + '.', {})['rec_name'] = rec_name def get_context(self, record, record_context=None, local=False): context = super(ReferenceField, self).get_context( record, record_context, local=local) if self.attrs.get('datetime_field'): context['_datetime'] = record.get_eval( )[self.attrs.get('datetime_field')] return context def get_on_change_value(self, record): if record.parent_name == self.name and record.parent: return record.parent.model_name, record.parent.get_on_change_value( skip={record.group.child_name}) return super(ReferenceField, self).get_on_change_value(record) def validation_domains(self, record, pre_validate=None): screen_domain, attr_domain = self.domains_get(record, pre_validate) return screen_domain def domains_get(self, record, pre_validate=None): if record.value.get(self.name): model = record.value[self.name][0] else: model = None screen_domain, attr_domain = super().domains_get( record, pre_validate=pre_validate) attr_domain = attr_domain.get(model, []) return screen_domain, attr_domain def domain_get(self, record): if record.value.get(self.name): model = record.value[self.name][0] else: model = None screen_domain, attr_domain = self.domains_get(record) screen_domain = filter_leaf(screen_domain, self.name, model) screen_domain = prepare_reference_domain(screen_domain, self.name) return concat(localize_domain( screen_domain, self.name, strip_target=True), attr_domain) def get_search_order(self, record): order = super().get_search_order(record) if order is not None: if record.value.get(self.name): model = record.value[self.name][0] else: model = None order = order.get(model) return order def get_models(self, record): screen_domain, attr_domain = self.domains_get(record) screen_domain = prepare_reference_domain(screen_domain, self.name) return extract_reference_models( concat(screen_domain, attr_domain), self.name) class _FileCache(object): def __init__(self, data=None): _, filename = tempfile.mkstemp(prefix='tryton_') self.path = Path(filename) self.suffixes = {} if data: with open(self.path, 'wb') as fp: fp.write(data) @property def data(self): with open(self.path, 'rb') as fp: return fp.read() def __del__(self): try: os.remove(self.path) except IOError: pass for path in self.suffixes.values(): try: os.remove(path) except IOError: pass def with_suffix(self, suffix): if suffix in self.suffixes: return self.suffixes[suffix] _, filename = tempfile.mkstemp(prefix='tryton_', suffix=suffix) self.suffixes[suffix] = path = Path(filename) with open(path, 'wb') as fp: fp.write(self.data) return path class BinaryField(Field): _default = None def _set_file_cache(self, record, data): if isinstance(data, str): data = data.encode('utf-8') file_cache = _FileCache(data) self.set(record, file_cache) return file_cache def get(self, record): result = record.value.get(self.name, self._default) if isinstance(result, _FileCache): try: result = result.data except IOError: result = self.get_data(record) return result def get_client(self, record): return self.get(record) def set_client(self, record, value, force_change=False): self._set_file_cache(record, value or b'') self.sig_changed(record) record.validate(softvalidation=True) record.set_modified(self.name) def get_size(self, record): result = record.value.get(self.name) or 0 if isinstance(result, _FileCache): result = os.stat(result.path).st_size elif isinstance(result, (str, bytes)): result = len(result) return result def get_data(self, record): if not isinstance(record.value.get(self.name), (str, bytes, _FileCache)): if record.id < 0: return b'' context = record.get_context() try: values, = RPCExecute('model', record.model_name, 'read', [record.id], [self.name], context=context) except RPCException: return b'' self._set_file_cache(record, values[self.name] or b'') return self.get(record) def get_filename(self, record, suffix=None): data = self.get_data(record) file_cache = record.value.get(self.name) if not isinstance(file_cache, _FileCache): file_cache = self._set_file_cache(record, data) if suffix: filename = file_cache.with_suffix(suffix) else: filename = file_cache.path return filename class DictField(Field): _default = {} _single_value = False def __init__(self, attrs): super(DictField, self).__init__(attrs) self.keys = {} def get(self, record): return (super().get(record) or self._default).copy() def get_client(self, record): return super().get_client(record) def validation_domains(self, record, pre_validate=None): screen_domain, attr_domain = self.domains_get(record, pre_validate) return screen_domain def domain_get(self, record): screen_domain, attr_domain = self.domains_get(record) return concat(localize_domain(screen_domain), attr_domain) def date_format(self, record): context = self.get_context(record) return common.date_format(context.get('date_format')) def time_format(self, record): return '%X' def add_keys(self, keys, record): schema_model = self.attrs['schema_model'] context = self.get_context(record) domain = self.domain_get(record) batchlen = min(10, CONFIG['client.limit']) for i in range(0, len(keys), batchlen): sub_keys = keys[i:i + batchlen] try: values = RPCExecute('model', schema_model, 'search_get_keys', [('name', 'in', sub_keys), domain], CONFIG['client.limit'], context=context) except RPCException: values = [] if not values: continue self.keys.update({k['name']: k for k in values}) def add_new_keys(self, key_ids, record): schema_model = self.attrs['schema_model'] context = self.get_context(record) try: new_fields = RPCExecute('model', schema_model, 'get_keys', key_ids, context=context) except RPCException: new_fields = [] new_keys = [] for new_field in new_fields: name = new_field['name'] new_keys.append(name) self.keys[name] = new_field return new_keys def validate(self, record, softvalidation=False, pre_validate=None): valid = super(DictField, self).validate( record, softvalidation, pre_validate) if self.attrs.get('readonly'): return valid decoder = PYSONDecoder() field_value = self.get_eval(record) domain = [] for key in field_value: if key not in self.keys: continue key_domain = self.keys[key].get('domain') if key_domain: domain.append(decoder.decode(key_domain)) valid_value = eval_domain(domain, field_value) if not valid_value: self.get_state_attrs(record)['invalid'] = 'domain' return valid and valid_value TYPES = { 'char': CharField, 'integer': IntegerField, 'float': FloatField, 'numeric': NumericField, 'many2one': M2OField, 'many2many': M2MField, 'one2many': O2MField, 'reference': ReferenceField, 'selection': SelectionField, 'multiselection': MultiSelectionField, 'boolean': BooleanField, 'datetime': DateTimeField, 'date': DateField, 'time': TimeField, 'timestamp': DateTimeField, 'timedelta': TimeDeltaField, 'one2one': O2OField, 'binary': BinaryField, 'dict': DictField, } ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685599.0 tryton-7.0.24/tryton/gui/window/view_form/model/group.py0000644000175000017500000004346414517761237021540 0ustar00cedced# This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. import operator from tryton import rpc from tryton.common import MODELACCESS, RPCException, RPCExecute from tryton.common.domain_inversion import is_leaf from .field import Field, M2OField, ReferenceField from .record import Record class Group(list): def __init__(self, model_name, fields, ids=None, parent=None, parent_name='', child_name='', context=None, domain=None, readonly=False, parent_datetime_field=None): super(Group, self).__init__() if domain is None: domain = [] self.__domain = domain self.__domain4inversion = None self.lock_signal = False self.screens = [] self.parent = parent self.parent_name = parent_name or '' self.children = [] self.child_name = child_name self.parent_datetime_field = parent_datetime_field self._context = context or {} self.model_name = model_name self.fields = {} self.load_fields(fields) self.current_idx = None self.load(ids) self.record_deleted, self.record_removed = [], [] self.on_write = set() self.__readonly = readonly self.__id2record = {} self.__field_childs = None self.exclude_field = None self.skip_model_access = False if self.parent and self.parent.model_name == model_name: self.parent.group.children.append(self) @property def readonly(self): # Must skip res.user for Preference windows if (self.context.get('_datetime') or (not (MODELACCESS[self.model_name]['write'] or MODELACCESS[self.model_name]['create']) and not self.skip_model_access)): return True return self.__readonly @readonly.setter def readonly(self, value): self.__readonly = value @property def domain(self): if self.parent and self.child_name: field = self.parent.group.fields[self.child_name] return [self.__domain, field.domain_get(self.parent)] return self.__domain def clean4inversion(self, domain): "This method will replace non relevant fields for domain inversion" if domain in ([], ()): return [] head, tail = domain[0], domain[1:] if head in ('AND', 'OR'): pass elif is_leaf(head): field = head[0] if (field in self.fields and self.fields[field].attrs.get('readonly')): head = [] else: head = self.clean4inversion(head) return [head] + self.clean4inversion(tail) def __get_domain4inversion(self): domain = self.domain if (self.__domain4inversion is None or self.__domain4inversion[0] != domain): self.__domain4inversion = ( domain, self.clean4inversion(domain)) domain, domain4inversion = self.__domain4inversion return domain4inversion domain4inversion = property(__get_domain4inversion) def insert(self, pos, record): assert record.group is self pos = min(pos, len(self)) if pos >= 1: self.__getitem__(pos - 1).next[id(self)] = record if pos < self.__len__(): record.next[id(self)] = self.__getitem__(pos) else: record.next[id(self)] = None super(Group, self).insert(pos, record) self.__id2record[record.id] = record if not self.lock_signal: self._group_list_changed('record-added', record, pos) def append(self, record): assert record.group is self if self.__len__() >= 1: self.__getitem__(self.__len__() - 1).next[id(self)] = record record.next[id(self)] = None super(Group, self).append(record) self.__id2record[record.id] = record if not self.lock_signal: self._group_list_changed( 'record-added', record, self.__len__() - 1) def _remove(self, record): idx = self.index(record) if idx >= 1: if idx + 1 < self.__len__(): self.__getitem__(idx - 1).next[id(self)] = \ self.__getitem__(idx + 1) else: self.__getitem__(idx - 1).next[id(self)] = None self._group_list_changed('record-removed', record, idx) super(Group, self).remove(record) del self.__id2record[record.id] def clear(self): # Use reversed order to minimize the cursor reposition as the cursor # has more chances to be on top of the list. length = self.__len__() for record in reversed(self[:]): # Destroy record before propagating the signal to recursively # destroy also the underlying records record.destroy() self._group_list_changed('record-removed', record, length - 1) self.pop() length -= 1 self.__id2record = {} self.record_removed, self.record_deleted = [], [] def move(self, record, pos): if self.__len__() > pos >= 0: idx = self.index(record) self._remove(record) if pos > idx: pos -= 1 self.insert(pos, record) else: self._remove(record) self.append(record) def __repr__(self): return '' % (self.model_name, id(self)) def load_fields(self, fields): for name, attr in fields.items(): field = Field.get_field(attr['type']) attr['name'] = name self.fields[name] = field(attr) def save(self): saved = [] for record in self: saved.append(record.save(force_reload=False)) if self.record_deleted: for record in self.record_deleted: self._remove(record) record.destroy() self.delete(self.record_deleted) del self.record_deleted[:] return saved def delete(self, records): if not records: return root_group = self.root_group assert all(r.model_name == self.model_name for r in records) assert all(r.group.root_group == root_group for r in records) records = [r for r in records if r.id >= 0] ctx = self.context ctx['_timestamp'] = {} for rec in records: ctx['_timestamp'].update(rec.get_timestamp()) record_ids = set(r.id for r in records) reload_ids = set(root_group.on_write_ids(list(record_ids))) reload_ids -= record_ids reload_ids = list(reload_ids) try: RPCExecute('model', self.model_name, 'delete', list(record_ids), context=ctx) except RPCException: return False for rec in records: rec.destroy() if reload_ids: root_group.reload(reload_ids) return True @property def root_group(self): root = self parent = self.parent while parent: root = parent.group parent = parent.parent return root def written(self, ids): if isinstance(ids, int): ids = [ids] ids = [x for x in self.on_write_ids(ids) or [] if x not in ids] if not ids: return self.root_group.reload(ids) return ids def reload(self, ids): for child in self.children: child.reload(ids) for id_ in ids: record = self.get(id_) if record and not record.modified: record.cancel() def on_write_ids(self, ids): if not self.on_write: return [] res = [] for fnct in self.on_write: try: res += RPCExecute('model', self.model_name, fnct, ids, context=self.context) except RPCException: return [] return list({}.fromkeys(res)) def load(self, ids, modified=False, position=-1): if not ids: return True if len(ids) > 1: self.lock_signal = True new_records = [] for id in ids: new_record = self.get(id) if not new_record: new_record = Record(self.model_name, id, group=self) if position == -1: self.append(new_record) else: self.insert(position, new_record) position += 1 new_records.append(new_record) # Remove previously removed or deleted records for record in self.record_removed[:]: if record.id in ids: self.record_removed.remove(record) for record in self.record_deleted[:]: if record.id in ids: self.record_deleted.remove(record) if self.lock_signal: self.lock_signal = False self._group_list_changed('group-cleared') if new_records and modified: for record in new_records: record.modified_fields.setdefault('id') self.record_modified() self.current_idx = 0 return True @property def context(self): return self._get_context(local=False) @property def local_context(self): return self._get_context(local=True) def _get_context(self, local=False): if not local: ctx = rpc.CONTEXT.copy() else: ctx = {} if self.parent: parent_context = self.parent.get_context(local=local) ctx.update(parent_context) if self.child_name in self.parent.group.fields: field = self.parent.group.fields[self.child_name] ctx.update(field.get_context( self.parent, parent_context, local=local)) ctx.update(self._context) if self.parent_datetime_field: ctx['_datetime'] = self.parent.get_eval( )[self.parent_datetime_field] return ctx @context.setter def context(self, value): self._context = value.copy() def add(self, record, position=-1, modified=True): if record.group is not self: record.group = self if record not in self: if position == -1: self.append(record) else: self.insert(position, record) for record_rm in self.record_removed: if record_rm.id == record.id: self.record_removed.remove(record_rm) for record_del in self.record_deleted: if record_del.id == record.id: self.record_deleted.remove(record_del) self.current_idx = position record.modified_fields.setdefault('id') if modified: # Set parent field to trigger on_change if self.parent and self.parent_name in self.fields: field = self.fields[self.parent_name] if isinstance(field, (M2OField, ReferenceField)): value = self.parent.id, '' if isinstance(field, ReferenceField): value = self.parent.model_name, value field.set_client(record, value) return record def set_sequence(self, field='sequence', position=-1): changed = False prev = None if position == 0: cmp = operator.gt else: cmp = operator.lt for record in self: # Assume not loaded records are correctly ordered # as far as we do not change any previous records. if record.get_loaded([field]) or changed or record.id < 0: if prev: index = prev[field].get(prev) else: index = None update = False value = record[field].get(record) if value is None: if index: update = True elif prev: if record.id >= 0: update = cmp(record.id, prev.id) elif position == 0: update = True elif value == index: if prev: if record.id >= 0: update = cmp(record.id, prev.id) elif position == 0: update = True elif value <= (index or 0): update = True if update: if index is None: index = 0 index += 1 record[field].set_client(record, index) changed = record prev = record if changed: self.record_modified() def new(self, default=True, obj_id=None, defaults=None): record = Record(self.model_name, obj_id, group=self) if default: record.default_get(defaults=defaults) return record def unremove(self, record, modified=True): if record in self.record_removed: self.record_removed.remove(record) if record in self.record_deleted: self.record_deleted.remove(record) if modified: self.record_modified() def remove(self, record, remove=False, modified=True, force_remove=False): idx = self.index(record) if record.id >= 0: if remove: if record in self.record_deleted: self.record_deleted.remove(record) if record not in self.record_removed: self.record_removed.append(record) else: if record in self.record_removed: self.record_removed.remove(record) if record not in self.record_deleted: self.record_deleted.append(record) record.modified_fields.setdefault('id') if record.id < 0 or force_remove: self._remove(record) if len(self): self.current_idx = min(idx, len(self) - 1) else: self.current_idx = None if modified: self.record_modified() def prev(self): if len(self) and self.current_idx is not None: self.current_idx = (self.current_idx - 1) % len(self) elif len(self): self.current_idx = 0 else: return None return self[self.current_idx] def __next__(self): if len(self) and self.current_idx is not None: self.current_idx = (self.current_idx + 1) % len(self) elif len(self): self.current_idx = 0 else: return None return self[self.current_idx] def add_fields(self, fields): to_add = {} for name, attr in fields.items(): if name not in self.fields: to_add[name] = attr else: self.fields[name].attrs.update(attr) self.load_fields(to_add) if not len(self): return True new = [] for record in self: if record.id < 0: new.append(record) if len(new) and len(to_add): try: values = RPCExecute('model', self.model_name, 'default_get', list(to_add.keys()), context=self.context) except RPCException: return False for record in new: record.set_default(values, modified=False) # Trigger modified only once with the last record self.record_modified() def get(self, id): 'Return record with the id' return self.__id2record.get(id) def id_changed(self, old_id): 'Update index for old id' record = self.__id2record[old_id] self.__id2record[record.id] = record del self.__id2record[old_id] def destroy(self): if self.parent: try: self.parent.group.children.remove(self) except ValueError: pass # One2Many connect the group to itself to send modified to the parent # but as we are destroying the group, we do not need to notify the # parent otherwise it will trigger unnecessary display. self.screens.clear() self.parent = None def get_by_path(self, path): 'return record by path' group = self record = None for child_name, id_ in path: record = group.get(id_) if not record: return None if not child_name: continue record[child_name] group = record.value.get(child_name) if not isinstance(group, Group): return None return record def _group_list_changed(self, action, *args): def groups(group): yield group while group.parent: if group.model_name != group.parent.model_name: break else: group = group.parent.group yield group for group in groups(self): for screen in group.screens: screen.group_list_changed(group, action, *args) def record_modified(self): if not self.parent: for screen in self.screens: screen.record_modified() else: self.parent.modified_fields.setdefault(self.child_name) self.parent.group.fields[self.child_name].sig_changed(self.parent) self.parent.validate(softvalidation=True) self.parent.group.record_modified() def record_notify(self, notifications): for screen in self.screens: screen.record_notify(notifications) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1734197759.0 tryton-7.0.24/tryton/gui/window/view_form/model/record.py0000644000175000017500000006501414727340777021662 0ustar00cedced# This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. import logging import tryton.common as common from tryton.common import RPCException, RPCExecute from tryton.config import CONFIG from tryton.pyson import PYSONDecoder from . import field as fields logger = logging.getLogger(__name__) class Record: id = -1 def __init__(self, model_name, obj_id, group=None): super(Record, self).__init__() self.model_name = model_name if obj_id is None: self.id = Record.id else: self.id = obj_id if self.id < 0: Record.id -= 1 self._loaded = set() self.group = group if group is not None: assert model_name == group.model_name self.state_attrs = {} self.modified_fields = {} self._timestamp = None self._write = True self._delete = True self.resources = None self.button_clicks = {} self.links_counts = {} self.next = {} # Used in Group list self.value = {} self.autocompletion = {} self.exception = False self.destroyed = False def __getitem__(self, name): self.load(name) if name != '*': return self.group.fields[name] def load(self, name, process_exception=True): if not self.destroyed and self.id >= 0 and name not in self._loaded: id2record = { self.id: self, } if name == '*': loading = 'eager' views = set() for field in self.group.fields.values(): if field.attrs.get('loading', 'eager') == 'lazy': loading = 'lazy' views |= field.views # Set a valid name for next loaded check for fname, field in self.group.fields.items(): if field.attrs.get('loading', 'eager') == loading: name = fname break else: loading = self.group.fields[name].attrs.get('loading', 'eager') views = self.group.fields[name].views if loading == 'eager': fields = ((fname, field) for fname, field in self.group.fields.items() if field.attrs.get('loading', 'eager') == 'eager') else: fields = self.group.fields.items() fnames = [fname for fname, field in fields if fname not in self._loaded and (not views or (views & field.views))] for fname in list(fnames): f_attrs = self.group.fields[fname].attrs if f_attrs['type'] in {'many2one', 'one2one', 'reference'}: fnames.append('%s.rec_name' % fname) elif (f_attrs['type'] in {'selection', 'multiselection'} and f_attrs.get('loading', 'lazy') == 'eager'): fnames.append('%s:string' % fname) if 'rec_name' not in fnames: fnames.append('rec_name') fnames.extend(['_timestamp', '_write', '_delete']) record_context = self.get_context() if loading == 'eager': limit = CONFIG['client.limit'] // min(len(fnames), 10) def filter_group(record): return (not record.destroyed and record.id >= 0 and name not in record._loaded) def filter_parent_group(record): return (filter_group(record) and record.id not in id2record and ((record.group == self.group) # Don't compute context for same group or (record.get_context() == record_context))) if self.parent and self.parent.model_name == self.model_name: group = sum(self.parent.group.children, []) filter_ = filter_parent_group else: group = self.group filter_ = filter_group if self in group: idx = group.index(self) length = len(group) n = 1 while len(id2record) < limit and (idx - n >= 0 or idx + n < length) and n < 2 * limit: if idx - n >= 0: record = group[idx - n] if filter_(record): id2record[record.id] = record if idx + n < length: record = group[idx + n] if filter_(record): id2record[record.id] = record n += 1 ctx = record_context.copy() ctx.update(dict(('%s.%s' % (self.model_name, fname), 'size') for fname, field in self.group.fields.items() if field.attrs['type'] == 'binary' and fname in fnames)) try: values = RPCExecute('model', self.model_name, 'read', list(id2record.keys()), fnames, context=ctx, process_exception=process_exception) except RPCException: for record in id2record.values(): record.exception = True if process_exception: values = [{'id': x} for x in id2record] default_values = {f: None for f in fnames if f != 'id'} for value in values: value.update(default_values) else: raise id2value = dict((value['id'], value) for value in values) for id, record in id2record.items(): value = id2value.get(id) if record and not record.destroyed and value: for key in record.modified_fields: value.pop(key, None) record.set(value, modified=False) def __repr__(self): return '' % (self.id, self.model_name, id(self)) @property def modified(self): if self.modified_fields: logger.info( "Modified fields %s of %s", list(self.modified_fields.keys()), self) return True else: return False @property def parent(self): return self.group.parent @property def parent_name(self): return self.group.parent_name @property def root_parent(self): parent = self while parent.parent: parent = parent.parent return parent @property def depth(self): parent = self.parent i = 0 while parent: i += 1 parent = parent.parent return i def children_group(self, field_name): if not field_name: return [] self._check_load([field_name]) group = self.value.get(field_name) if group is None: return None if id(group.fields) != id(self.group.fields): self.group.fields.update(group.fields) group.fields = self.group.fields group.on_write = self.group.on_write group.readonly = self.group.readonly group._context.update(self.group._context) return group def get_path(self, group): path = [] i = self child_name = '' while i: path.append((child_name, i.id)) if i.group is group: break child_name = i.group.child_name i = i.parent path.reverse() return tuple(path) def get_index_path(self, group=None): path = [] record = self while record: path.append(record.group.index(record)) if record.group is group: break record = record.parent path.reverse() return tuple(path) def get_removed(self): if self.group is not None: return self in self.group.record_removed return False removed = property(get_removed) def get_deleted(self): if self.group is not None: return self in self.group.record_deleted return False deleted = property(get_deleted) def get_readonly(self): return (self.deleted or self.removed or self.exception or self.group.readonly or not self._write) readonly = property(get_readonly) @property def deletable(self): return self._delete def fields_get(self): return self.group.fields def _check_load(self, fields=None): if fields is not None: if not self.get_loaded(fields): self.reload(fields) return True return False if not self.loaded: self.reload() return True return False def get_loaded(self, fields=None): if fields: return set(fields) <= (self._loaded | set(self.modified_fields)) return set(self.group.fields.keys()) == self._loaded loaded = property(get_loaded) def get(self): value = {} for name, field in self.group.fields.items(): if (field.attrs.get('readonly') and not (isinstance(field, fields.O2MField) and not isinstance(field, fields.M2MField))): continue if field.name not in self.modified_fields and self.id >= 0: continue value[name] = field.get(self) # Sending an empty x2MField breaks ModelFieldAccess.check if isinstance(field, fields.O2MField) and not value[name]: del value[name] return value def get_eval(self): value = {} for name, field in self.group.fields.items(): if name not in self._loaded and self.id >= 0: continue value[name] = field.get_eval(self) value['id'] = self.id return value def get_on_change_value(self, skip=None): value = {} for name, field in self.group.fields.items(): if skip and name in skip: continue if (self.id >= 0 and (name not in self._loaded or name not in self.modified_fields)): continue value[name] = field.get_on_change_value(self) value['id'] = self.id return value def cancel(self): self._loaded.clear() self.value = {} self.modified_fields.clear() self._timestamp = None self.button_clicks.clear() self.links_counts.clear() self.exception = False def get_timestamp(self): result = {self.model_name + ',' + str(self.id): self._timestamp} for name, field in self.group.fields.items(): if name in self._loaded: result.update(field.get_timestamp(self)) return result def pre_validate(self): if not self.modified_fields: return True values = self._get_on_change_args(['id'] + list(self.modified_fields)) try: RPCExecute('model', self.model_name, 'pre_validate', values, context=self.get_context()) except RPCException: return False return True def save(self, force_reload=True): if self.id < 0 or self.modified: value = self.get() if self.id < 0: try: res, = RPCExecute('model', self.model_name, 'create', [value], context=self.get_context()) except RPCException: return False old_id = self.id self.id = res self.group.id_changed(old_id) elif self.modified: if value: context = self.get_context() context = context.copy() context['_timestamp'] = self.get_timestamp() try: RPCExecute('model', self.model_name, 'write', [self.id], value, context=context) except RPCException: return False self.cancel() if force_reload: self.reload() if self.group: self.group.written(self.id) if self.parent: self.parent.modified_fields.pop(self.group.child_name, None) self.parent.save(force_reload=force_reload) return self.id def default_get(self, defaults=None): vals = {} if len(self.group.fields): context = self.get_context() if defaults is not None: for name, value in defaults.items(): context.setdefault(f'default_{name}', value) try: vals = RPCExecute('model', self.model_name, 'default_get', list(self.group.fields.keys()), context=context) except RPCException: return vals if (self.parent and self.parent_name in self.group.fields): parent_field = self.group.fields[self.parent_name] if isinstance(parent_field, fields.ReferenceField): vals[self.parent_name] = ( self.parent.model_name, self.parent.id) elif (self.group.fields[self.parent_name].attrs['relation'] == self.group.parent.model_name): vals[self.parent_name] = self.parent.id self.set_default(vals) return vals def rec_name(self): try: return RPCExecute('model', self.model_name, 'read', [self.id], ['rec_name'], context=self.get_context())[0]['rec_name'] except RPCException: return '' def validate(self, fields=None, softvalidation=False, pre_validate=None): if isinstance(fields, list) and fields: self._check_load(fields) elif fields is None: self._check_load() res = True for field_name, field in list(self.group.fields.items()): if fields is not None and field_name not in fields: continue if field.attrs.get('readonly'): continue if field_name == self.group.exclude_field: continue if not field.validate(self, softvalidation, pre_validate): res = False return res def _get_invalid_fields(self): fields = {} for fname, field in self.group.fields.items(): invalid = field.get_state_attrs(self).get('invalid') if invalid: fields[fname] = invalid return fields invalid_fields = property(_get_invalid_fields) def get_context(self, local=False): if not local: return self.group.context else: return self.group.local_context def set_default(self, val, modified=True, validate=True): fieldnames = [] for fieldname, value in list(val.items()): if fieldname in {'_write', '_delete', '_timestamp'}: setattr(self, fieldname, value) continue if fieldname not in self.group.fields: continue if fieldname == self.group.exclude_field: continue if isinstance(self.group.fields[fieldname], (fields.M2OField, fields.ReferenceField)): related = fieldname + '.' self.value[related] = val.get(related) or {} self.group.fields[fieldname].set_default(self, value) self._loaded.add(fieldname) fieldnames.append(fieldname) self.on_change(fieldnames) self.on_change_with(fieldnames) if validate: self.validate(softvalidation=True) if modified: self.set_modified() def set(self, val, modified=True, validate=True): later = {} fieldnames = [] for fieldname, value in val.items(): if fieldname == '_timestamp': # Always keep the older timestamp if not self._timestamp: self._timestamp = value continue if fieldname in {'_write', '_delete'}: setattr(self, fieldname, value) continue if fieldname not in self.group.fields: if fieldname == 'rec_name': self.value['rec_name'] = value continue field = self.group.fields[fieldname] if isinstance(field, fields.O2MField): later[fieldname] = value continue if isinstance(field, (fields.M2OField, fields.ReferenceField)): related = fieldname + '.' self.value[related] = val.get(related) or {} elif isinstance(field, ( fields.SelectionField, fields.MultiSelectionField)): related = fieldname + ':string' if fieldname + ':string' in val: self.value[related] = val[related] else: self.value.pop(related, None) self.group.fields[fieldname].set(self, value) self._loaded.add(fieldname) fieldnames.append(fieldname) for fieldname, value in later.items(): self.group.fields[fieldname].set(self, value) self._loaded.add(fieldname) fieldnames.append(fieldname) if validate: self.validate(fieldnames, softvalidation=True) if modified: self.set_modified() def set_on_change(self, values): for fieldname, value in list(values.items()): if fieldname not in self.group.fields: continue if isinstance(self.group.fields[fieldname], (fields.M2OField, fields.ReferenceField)): related = fieldname + '.' self.value[related] = values.get(related) or {} # Load fieldname before setting value self[fieldname].set_on_change(self, value) def reload(self, fields=None): if self.id < 0: return if not fields: self['*'] else: for field in fields: self[field] self.validate(fields or []) def reset(self, value): self.cancel() self.set(value, modified=False) if self.parent: self.parent.on_change([self.group.child_name]) self.parent.on_change_with([self.group.child_name]) self.set_modified() def expr_eval(self, expr): if not isinstance(expr, str): return expr if not expr: return elif expr == '[]': return [] elif expr == '{}': return {} ctx = self.get_eval() ctx['context'] = self.get_context() ctx['active_model'] = self.model_name ctx['active_id'] = self.id if self.parent and self.parent_name: ctx['_parent_' + self.parent_name] = \ common.EvalEnvironment(self.parent) val = PYSONDecoder(ctx).decode(expr) return val def _get_on_change_args(self, args): res = {} values = common.EvalEnvironment(self, 'on_change') for arg in args: scope = values for i in arg.split('.'): if i not in scope: break scope = scope[i] else: res[arg] = scope return res def on_change(self, fieldnames): values = {} for fieldname in fieldnames: on_change = self.group.fields[fieldname].attrs.get('on_change') if not on_change: continue values.update(self._get_on_change_args(on_change)) if values: try: if len(fieldnames) == 1 or 'id' not in values: changes = [] for fieldname in fieldnames: changes.append(RPCExecute( 'model', self.model_name, 'on_change_' + fieldname, values, context=self.get_context())) else: changes = [RPCExecute( 'model', self.model_name, 'on_change', values, fieldnames, context=self.get_context())] except RPCException: pass else: for change in changes: self.set_on_change(change) notification_fields = common.MODELNOTIFICATION.get(self.model_name) if set(fieldnames) & set(notification_fields): values = self._get_on_change_args(notification_fields) try: notifications = RPCExecute( 'model', self.model_name, 'on_change_notify', values, context=self.get_context()) except RPCException: pass else: self.group.record_notify(notifications) def on_change_with(self, field_names): field_names = set(field_names) fieldnames = set() values = {} later = set() for fieldname in self.group.fields: on_change_with = self.group.fields[fieldname].attrs.get( 'on_change_with') if not on_change_with: continue if not field_names & set(on_change_with): continue if fieldnames & set(on_change_with): later.add(fieldname) continue fieldnames.add(fieldname) values.update( self._get_on_change_args(on_change_with + [fieldname])) if isinstance(self.group.fields[fieldname], (fields.M2OField, fields.ReferenceField)): self.value.pop(fieldname + '.', None) if fieldnames: try: if len(fieldnames) == 1 or 'id' not in values: changed = {} for fieldname in fieldnames: changed.update(RPCExecute( 'model', self.model_name, 'on_change_with_' + fieldname, values, context=self.get_context())) else: changed = RPCExecute( 'model', self.model_name, 'on_change_with', values, list(fieldnames), context=self.get_context()) except RPCException: return self.set_on_change(changed) if later: values = {} for fieldname in later: on_change_with = self.group.fields[fieldname].attrs.get( 'on_change_with') values.update( self._get_on_change_args(on_change_with + [fieldname])) try: if len(later) == 1 or 'id' not in values: changed = {} for fieldname in later: changed.update(RPCExecute( 'model', self.model_name, 'on_change_with_' + fieldname, values, context=self.get_context())) else: changed = RPCExecute( 'model', self.model_name, 'on_change_with', values, list(later), context=self.get_context()) except RPCException: return self.set_on_change(changed) def autocomplete_with(self, field_name): for fieldname, fieldinfo in self.group.fields.items(): autocomplete = fieldinfo.attrs.get('autocomplete', []) if field_name not in autocomplete: continue self.do_autocomplete(fieldname) def do_autocomplete(self, fieldname): self.autocompletion[fieldname] = [] autocomplete = self.group.fields[fieldname].attrs['autocomplete'] args = self._get_on_change_args(autocomplete) try: res = RPCExecute( 'model', self.model_name, 'autocomplete_' + fieldname, args, context=self.get_context()) except RPCException: # ensure res is a list res = [] self.autocompletion[fieldname] = res def on_scan_code(self, code, depends): depends = self.expr_eval(depends) values = self._get_on_change_args(depends) changes = RPCExecute( 'model', self.model_name, 'on_scan_code', values, code, context=self.get_context(), process_exception=False) self.set_on_change(changes) self.set_modified() return bool(changes) def set_field_context(self): from .group import Group for name, field in self.group.fields.items(): value = self.value.get(name) if not isinstance(value, Group): continue context = field.attrs.get('context') if context: value.context = self.expr_eval(context) def get_resources(self, reload=False): if self.id >= 0 and (not self.resources or reload): try: self.resources = RPCExecute( 'model', self.model_name, 'resources', self.id, context=self.get_context()) except RPCException: pass return self.resources def get_button_clicks(self, name): if self.id < 0: return clicks = self.button_clicks.get(name) if clicks is not None: return clicks try: clicks = RPCExecute('model', 'ir.model.button.click', 'get_click', self.model_name, name, self.id) self.button_clicks[name] = clicks except RPCException: return return clicks def set_modified(self, field=None): if field: self.modified_fields.setdefault(field) self.group.record_modified() def destroy(self): for v in self.value.values(): if hasattr(v, 'destroy'): v.destroy() self.destroyed = True ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1745680285.210778 tryton-7.0.24/tryton/gui/window/view_form/screen/0000755000175000017500000000000015003173635020164 5ustar00cedced././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685599.0 tryton-7.0.24/tryton/gui/window/view_form/screen/__init__.py0000644000175000017500000000027714517761237022315 0ustar00cedced# This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. from .screen import Screen __all__ = [Screen] ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1744651672.0 tryton-7.0.24/tryton/gui/window/view_form/screen/screen.py0000644000175000017500000015134614777242630022040 0ustar00cedced# This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. "Screen" import calendar import collections import datetime import functools import gettext import json import logging import urllib.parse import xml.dom.minidom from operator import itemgetter from gi.repository import GLib, Gtk from tryton.action import Action from tryton.common import ( MODELACCESS, RPCContextReload, RPCException, RPCExecute, node_attributes, sur, warning) from tryton.common.domain_inversion import canonicalize from tryton.common.domain_parser import DomainParser from tryton.config import CONFIG from tryton.gui.window.infobar import InfoBar from tryton.gui.window.view_form.model.group import Group from tryton.gui.window.view_form.view import View from tryton.gui.window.view_form.view.screen_container import ScreenContainer from tryton.jsonrpc import JSONEncoder from tryton.pyson import PYSONDecoder from tryton.rpc import clear_cache _ = gettext.gettext logger = logging.getLogger(__name__) class Screen: "Screen" # Width of tree columns per model # It is shared with all connection but it is the price for speed. tree_column_width = collections.defaultdict(lambda: {}) tree_column_optional = {} def __init__(self, model_name, **attributes): context = attributes.get('context', {}) self._current_domain = [] self.limit = attributes.get('limit', CONFIG['client.limit']) self.offset = 0 self.windows = [] self.readonly = attributes.get('readonly', False) if not (MODELACCESS[model_name]['write'] or MODELACCESS[model_name]['create']): self.readonly = True self.search_count = 0 if not attributes.get('row_activate'): self.row_activate = self.default_row_activate else: self.row_activate = attributes['row_activate'] self.domain = attributes.get('domain', []) self.context_domain = attributes.get('context_domain') self.size_limit = None self.views_preload = attributes.get('views_preload', {}) self.model_name = model_name self.views = [] self.view_ids = attributes.get('view_ids', [])[:] self.parent = None self.parent_name = None self.exclude_field = attributes.get('exclude_field') self.filter_widget = None self.tree_states = collections.defaultdict( lambda: collections.defaultdict(lambda: None)) self.tree_states_done = set() self.__group = None self.__current_record = None self.new_group(context or {}) self.current_record = None self.screen_container = ScreenContainer( self, attributes.get('tab_domain')) self.screen_container.alternate_view = attributes.get( 'alternate_view', False) self.widget = self.screen_container.widget_get() self.breadcrumb = attributes.get('breadcrumb') or [] self.context_screen = None if attributes.get('context_model'): self.context_screen = Screen( attributes['context_model'], mode=['form'], context=context) self.context_screen.new() context_widget = self.context_screen.widget def walk_descendants(widget): yield widget if not hasattr(widget, 'get_children'): return for child in widget.get_children(): for widget in walk_descendants(child): yield widget for widget in reversed(list(walk_descendants(context_widget))): if isinstance(widget, Gtk.Entry): widget.connect_after( 'activate', self.screen_container.activate) elif isinstance(widget, Gtk.CheckButton): widget.connect_after( 'toggled', self.screen_container.activate) def remove_bin(widget): assert isinstance(widget, (Gtk.ScrolledWindow, Gtk.Viewport)) parent = widget.get_parent() parent.remove(widget) child = widget.get_child() while isinstance(child, (Gtk.ScrolledWindow, Gtk.Viewport)): child = child.get_child() child.get_parent().remove(child) parent.add(child) return child # Remove first level Viewport and ScrolledWindow to fill the Vbox remove_bin(self.context_screen.screen_container.viewport) if self.context_screen.current_view: remove_bin( self.context_screen.current_view.widget.get_children()[0]) self.screen_container.filter_vbox.pack_start( context_widget, expand=False, fill=True, padding=0) self.screen_container.filter_vbox.reorder_child( context_widget, 0) self.context_screen.widget.show() self.__current_view = 0 self.search_value = attributes.get('search_value') self.fields_view_tree = {} self.order = self.default_order = attributes.get('order') self.view_to_load = [] self._domain_parser = {} self.pre_validate = False mode = attributes.get('mode') if mode is None: mode = ['tree', 'form'] self.view_to_load = mode[:] if self.view_ids or self.view_to_load: self.switch_view() def __repr__(self): return '' % (self.model_name, id(self)) @property def readonly(self): return (self.__readonly or any(r.readonly for r in self.selected_records)) @readonly.setter def readonly(self, value): self.__readonly = value @property def deletable(self): return all(r.deletable for r in self.selected_records) @property def count_limit(self): return self.limit * 100 + self.offset def search_active(self, active=True): if active and not self.parent: self.screen_container.show_filter() else: self.screen_container.hide_filter() @property def domain_parser(self): view_id = self.current_view.view_id if self.current_view else None if view_id in self._domain_parser: return self._domain_parser[view_id] if view_id not in self.fields_view_tree: context = self.context context['view_tree_width'] = CONFIG['client.save_tree_width'] try: self.fields_view_tree[view_id] = view_tree = RPCExecute( 'model', self.model_name, 'fields_view_get', False, 'tree', context=context) except RPCException: view_tree = { 'fields': {}, } else: view_tree = self.fields_view_tree[view_id] fields = view_tree['fields'].copy() for name in fields: if fields[name]['type'] not in { 'selection', 'multiselection', 'reference'}: continue if isinstance(fields[name]['selection'], (tuple, list)): continue props = fields[name] = fields[name].copy() props['selection'] = self.get_selection(props) if 'arch' in view_tree: # Filter only fields in XML view xml_dom = xml.dom.minidom.parseString(view_tree['arch']) root_node, = xml_dom.childNodes ofields = collections.OrderedDict() for node in root_node.childNodes: if node.nodeName != 'field': continue attributes = node_attributes(node) name = attributes['name'] # If a field is defined multiple times in the XML, # take only the first definition if name in ofields: continue ofields[name] = fields[name] for attr in ['string', 'factor']: if attributes.get(attr): ofields[name][attr] = attributes[attr] symbol = attributes.get('symbol') if symbol and symbol not in ofields: ofields[symbol] = fields[symbol] fields = ofields # Add common fields for name, string, type_ in ( ('id', _('ID'), 'integer'), ('create_uid', _('Created by'), 'many2one'), ('create_date', _('Created at'), 'datetime'), ('write_uid', _('Edited by'), 'many2one'), ('write_date', _('Edited at'), 'datetime'), ): if name not in fields: fields[name] = { 'string': string, 'name': name, 'type': type_, } if type_ == 'datetime': fields[name]['format'] = '"%H:%M:%S"' domain_parser = DomainParser(fields, self.context) self._domain_parser[view_id] = domain_parser return domain_parser def get_selection(self, props): try: change_with = props.get('selection_change_with') if change_with: selection = RPCExecute('model', self.model_name, props['selection'], dict((p, None) for p in change_with)) else: selection = RPCExecute('model', self.model_name, props['selection']) except RPCException: selection = [] selection.sort(key=itemgetter(1)) return selection def search_prev(self, search_string): if self.limit: self.offset = max(self.offset - self.limit, 0) self.search_filter(search_string=search_string) def search_next(self, search_string): if self.limit: self.offset += self.limit self.search_filter(search_string=search_string) def search_complete(self, search_string): return list(self.domain_parser.completion(search_string)) def search_filter(self, search_string=None, only_ids=False): if self.context_screen and not only_ids: context_record = self.context_screen.current_record if not context_record.validate(): self.clear() self.context_screen.display(set_cursor=True) return False context = self.local_context screen_context = self.context_screen.get_on_change_value() screen_context.pop('id') context.update(screen_context) self.new_group(context) domain = self.search_domain(search_string, True) if (canonicalized := canonicalize(domain)) != self._current_domain: self._current_domain = canonicalized self.offset = 0 context = self.context if (self.screen_container.but_active.props.visible and self.screen_container.but_active.get_active()): context['active_test'] = False ids = [] while True: try: ids = RPCExecute('model', self.model_name, 'search', domain, self.offset, self.limit, self.order, context=context) except RPCException: break if ids or self.offset <= 0: break self.offset = max(self.offset - self.limit, 0) if not only_ids: if self.limit is not None and len(ids) == self.limit: try: self.search_count = RPCExecute( 'model', self.model_name, 'search_count', domain, 0, self.count_limit, context=context) except RPCException: self.search_count = 0 else: self.search_count = len(ids) self.screen_container.but_prev.set_sensitive(bool(self.offset)) if (self.limit is not None and len(ids) == self.limit and self.search_count > self.limit + self.offset): self.screen_container.but_next.set_sensitive(True) else: self.screen_container.but_next.set_sensitive(False) if only_ids: return ids self.clear() self.load(ids) self.count_tab_domain() return bool(ids) def search_domain(self, search_string=None, set_text=False, with_tab=True): domain = [] # Test first parent to avoid calling unnecessary domain_parser if not self.parent and self.domain_parser: if search_string is not None: domain = self.domain_parser.parse(search_string) else: domain = self.search_value self.search_value = None if set_text: self.screen_container.set_text( self.domain_parser.string(domain)) else: domain = [('id', 'in', [x.id for x in self.group])] if domain: if self.domain: domain = ['AND', domain, self.domain] else: domain = self.domain if (self.screen_container.but_active.props.visible and self.screen_container.but_active.get_active()): if domain: domain = [domain, ('active', '=', False)] else: domain = [('active', '=', False)] if self.current_view and self.current_view.view_type == 'calendar': if domain: domain = ['AND', domain, self.current_view.current_domain()] else: domain = self.current_view.current_domain() if self.context_domain: decoder = PYSONDecoder(self.context) domain = ['AND', domain, decoder.decode(self.context_domain)] if with_tab: tab_domain = self.screen_container.get_tab_domain() if tab_domain: domain = ['AND', domain, tab_domain] return domain def count_tab_domain(self, current=False): def set_tab_counter(count, idx): try: count = count() except RPCException: count = None self.screen_container.set_tab_counter(count, idx) screen_domain = self.search_domain( self.screen_container.get_text(), with_tab=False) index = self.screen_container.get_tab_index() for idx, (name, domain, count) in enumerate( self.screen_container.tab_domain): if not count or (current and idx != index): continue domain = ['AND', domain, screen_domain] set_tab_counter(lambda: None, idx) RPCExecute('model', self.model_name, 'search_count', domain, 0, 1000, context=self.context, callback=functools.partial(set_tab_counter, idx=idx)) @property def context(self): context = self.group.context if self.context_screen: context['context_model'] = self.context_screen.model_name return context @property def local_context(self): context = self.group.local_context if self.context_screen: context['context_model'] = self.context_screen.model_name return context def __get_group(self): return self.__group def __set_group(self, group): fields = {} fields_views = {} if self.group is not None: for name, field in self.group.fields.items(): fields[name] = field.attrs fields_views[name] = field.views if self in self.group.screens: self.group.screens.remove(self) group.on_write.update(self.group.on_write) self.tree_states_done.clear() self.__group = group self.group.screens.append(self) self.parent = group.parent self.parent_name = group.parent_name if self.parent: self.filter_widget = None self.order = None self.__group.add_fields(fields) self.current_record = None for name, views in fields_views.items(): self.__group.fields[name].views.update(views) self.__group.exclude_field = self.exclude_field group = property(__get_group, __set_group) def new_group(self, context=None): context = context if context is not None else self.context self.group = Group(self.model_name, {}, domain=self.domain, context=context, readonly=self.__readonly) def group_list_changed(self, group, action, *args): for view in self.views: if hasattr(view, 'group_list_changed'): view.group_list_changed(group, action, *args) def record_modified(self, display=True): for window in self.windows: if hasattr(window, 'record_modified'): window.record_modified() if display: self.display() def record_notify(self, notifications): for window in self.windows: if isinstance(window, InfoBar): window.info_bar_refresh('notification') for type_, message in notifications: type_ = { 'info': Gtk.MessageType.INFO, 'warning': Gtk.MessageType.WARNING, 'error': Gtk.MessageType.ERROR, }.get(type_, Gtk.MessageType.WARNING) window.info_bar_add(message, type_, 'notification') def record_message(self, position, size, max_size, record_id): for window in self.windows: if hasattr(window, 'record_message'): window.record_message(position, size, max_size, record_id) def record_saved(self): for window in self.windows: if hasattr(window, 'record_saved'): window.record_saved() def update_resources(self, resources): for window in self.windows: if hasattr(window, 'update_resources'): window.update_resources(resources) def has_update_resources(self): return any(hasattr(w, 'update_resources') for w in self.windows) def __get_current_record(self): if (self.__current_record is not None and self.__current_record.group is None): self.__current_record = None return self.__current_record def __set_current_record(self, record): if self.__current_record == record and record: return self.__current_record = record if record: try: pos = self.group.index(record) + self.offset + 1 except ValueError: # XXX offset? pos = record.get_index_path() else: pos = 0 self.record_message( pos, len(self.group) + self.offset, self.search_count, record and record.id) self.update_resources(record.resources if record else None) # update resources after 1 second GLib.timeout_add(1000, self._update_resources, record) current_record = property(__get_current_record, __set_current_record) def _update_resources(self, record): if (record and record == self.current_record and self.has_update_resources()): self.update_resources(record.get_resources()) return False def destroy(self): self.windows.clear() for view in self.views: view.destroy() del self.views[:] self.group.destroy() def default_row_activate(self): if (self.current_view and self.current_view.view_type == 'tree' and int(self.current_view.attributes.get('keyword_open', 0))): return Action.exec_keyword('tree_open', { 'model': self.model_name, 'id': self.current_record.id if self.current_record else None, 'ids': [r.id for r in self.selected_records], }, context=self.local_context, warning=False) else: if not self.modified(): self.switch_view(view_type='form') return True @property def number_of_views(self): return len(self.views) + len(self.view_to_load) @property def view_index(self): return self.__current_view @property def next_view_type(self): views = self.views + self.view_to_load next_view_index = (self.view_index + 1) % len(views) next_view = views[next_view_index] if not isinstance(next_view, str): next_view = next_view.view_type return next_view def switch_view( self, view_type=None, view_id=None, creatable=None, display=True): if view_id is not None: view_id = int(view_id) if self.current_view: self.current_view.set_value() if (self.current_record and self.current_record not in self.current_record.group): self.current_record = None fields = self.current_view.get_fields() if (self.current_record and self.current_view.editable and not self.current_record.validate(fields)): self.screen_container.set(self.current_view.widget) self.set_cursor() self.current_view.display() return def found(): if not self.current_view: return False result = True if view_type is not None: result &= self.current_view.view_type == view_type if view_id is not None: result &= self.current_view.view_id == view_id if creatable is not None: result &= self.current_view.creatable == creatable return result for i in range(len(self.views) + len(self.view_to_load)): if len(self.view_to_load): self.load_view_to_load() self.__current_view = len(self.views) - 1 elif (view_id is not None and view_id not in {v.view_id for v in self.views}): self.add_view_id(view_id, view_type) self.__current_view = len(self.views) - 1 break else: self.__current_view = ((self.__current_view + 1) % len(self.views)) if found(): break self.screen_container.set(self.current_view.widget) if display: self.display() # Postpone set of the cursor to ensure widgets are allocated GLib.idle_add(self.set_cursor) def load_view_to_load(self): if len(self.view_to_load): if self.view_ids: view_id = self.view_ids.pop(0) else: view_id = None view_type = self.view_to_load.pop(0) self.add_view_id(view_id, view_type) def add_view_id(self, view_id, view_type): if view_id and str(view_id) in self.views_preload: view = self.views_preload[str(view_id)] elif not view_id and view_type in self.views_preload: view = self.views_preload[view_type] else: context = self.context context['view_tree_width'] = CONFIG['client.save_tree_width'] try: view = RPCExecute( 'model', self.model_name, 'fields_view_get', view_id, view_type, context=context) except RPCException: return return self.add_view(view) def add_view(self, view): arch = view['arch'] fields = view['fields'] view_id = view['view_id'] xml_dom = xml.dom.minidom.parseString(arch) root, = xml_dom.childNodes if root.tagName == 'tree': self.fields_view_tree[view_id] = view # Ensure that loading is always lazy for fields on form view # and always eager for fields on tree or graph view if root.tagName == 'form': loading = 'lazy' else: loading = 'eager' for field in fields: if field not in self.group.fields or loading == 'eager': fields[field]['loading'] = loading else: fields[field]['loading'] = \ self.group.fields[field].attrs['loading'] self.group.add_fields(fields) for field in fields: self.group.fields[field].views.add(view_id) view = View.parse( self, view_id, view['type'], xml_dom, view.get('field_childs')) self.views.append(view) return view def new(self, default=True, defaults=None): previous_view = self.current_view if self.current_view and self.current_view.view_type == 'calendar': selected_date = self.current_view.get_selected_date() if self.current_view and not self.current_view.creatable: self.switch_view(creatable=True) if not self.current_view.creatable: return None if self.current_record: group = self.current_record.group else: group = self.group record = group.new(default, defaults=defaults) group.add(record, self.new_position) if previous_view.view_type == 'calendar': previous_view.set_default_date(record, selected_date) self.current_record = record self.display() # Postpone set of the cursor to ensure widgets are allocated GLib.idle_add(self.set_cursor, True) return self.current_record @property def new_position(self): if self.order is not None: order = self.order else: order = self.default_order if order: for oexpr, otype in order: if oexpr == 'id' and otype: if otype.startswith('DESC'): return 0 elif otype.startswith('ASC'): return -1 if self.parent: return -1 else: return 0 def set_on_write(self, func_name): if func_name: self.group.on_write.add(func_name) def cancel_current(self, initial_value=None): if self.current_record: self.current_record.cancel() if self.current_record.id < 0: if initial_value is not None: self.current_record.reset(initial_value) else: self.remove(records=[self.current_record]) def save_current(self): if not self.current_record: if (self.current_view and self.current_view.view_type == 'tree' and len(self.group)): self.current_record = self.group[0] else: return True saved = False record_id = None if self.current_view: self.current_view.set_value() fields = self.current_view.get_fields() path = self.current_record.get_path(self.group) if self.current_view and self.current_view.view_type == 'tree': # False value must be not saved saved = all(( x is not False and x >= 0 for x in self.group.save())) record_id = self.current_record.id if self.current_record else None elif self.current_record.validate(fields): record_id = self.current_record.save(force_reload=True) # False value must be not saved saved = record_id is not False and record_id >= 0 elif self.current_view: self.set_cursor() self.current_view.display() return False if path and record_id: path = path[:-1] + ((path[-1][0], record_id),) self.current_record = self.group.get_by_path(path) self.display() self.record_saved() return saved def __get_current_view(self): if not len(self.views): return None return self.views[self.__current_view] current_view = property(__get_current_view) def set_cursor(self, new=False, reset_view=True): current_view = self.current_view if not current_view: return elif current_view.view_type in ('tree', 'form', 'list-form'): current_view.set_cursor(new=new, reset_view=reset_view) def get(self): if not self.current_record: return None if self.current_view: self.current_view.set_value() return self.current_record.get() def get_on_change_value(self): if not self.current_record: return None if self.current_view: self.current_view.set_value() return self.current_record.get_on_change_value() def modified(self): if self.current_view and self.current_view.view_type != 'tree': if self.current_record: if self.current_record.modified or self.current_record.id < 0: return True else: for record in self.group: if record.modified or record.id < 0: return True if self.current_view and self.current_view.modified: return True return False def reload(self, ids, written=False): self.group.reload(ids) if written: self.group.written(ids) if self.parent: self.parent.root_parent.reload() self.display() def unremove(self): records = self.selected_records for record in records: self.group.unremove(record) def remove(self, delete=False, remove=False, force_remove=False, records=None): records = records or self.selected_records if not records: return if delete: # Must delete children records before parent records.sort(key=lambda r: r.depth, reverse=True) if not self.group.delete(records): return False for record in records: # set current model to None to prevent __select_changed # to save the previous_model as it can be already deleted. self.current_record = None record.group.remove( record, remove=remove, modified=False, force_remove=force_remove) # set current_record to None to prevent __select_changed # to set deleted record as current_record self.current_record = None # call only once record.set_modified() if delete: for record in records: if record in record.group.record_deleted: record.group.record_deleted.remove(record) if record in record.group.record_removed: record.group.record_removed.remove(record) if record.parent: # Save parent without deleted children record.parent.save(force_reload=False) record.destroy() self.current_record = None self.set_cursor() self.display() return True def copy(self): ids = [r.id for r in self.selected_records] try: new_ids = RPCExecute('model', self.model_name, 'copy', ids, {}, context=self.context) except RPCException: return False self.group.load(new_ids, position=self.new_position) if new_ids: self.current_record = self.group.get(new_ids[0]) self.display(set_cursor=True) return True def set_tree_state(self): view = self.current_view if not view: return if view.view_type not in {'tree', 'form', 'list-form'}: return if id(view) in self.tree_states_done: return if view.view_type == 'form' and self.tree_states_done: return if (view.view_type in {'tree', 'list-form'} and not int(view.attributes.get('tree_state', False))): # Mark as done to not set later when the view_type change self.tree_states_done.add(id(view)) parent = self.parent.id if self.parent else None if parent is not None and parent < 0: return state = self.tree_states[parent][view.children_field] if state: expanded_nodes, selected_nodes = state else: expanded_nodes, selected_nodes = [], [] if view.view_type in {'tree', 'list-form'}: if (state is None and CONFIG['client.save_tree_state'] and int(view.attributes.get('tree_state', False))): json_domain = self.get_tree_domain(parent) try: expanded_nodes, selected_nodes = RPCExecute('model', 'ir.ui.view_tree_state', 'get', self.model_name, json_domain, view.children_field) expanded_nodes = json.loads(expanded_nodes) selected_nodes = json.loads(selected_nodes) except RPCException: logger.warn( 'Unable to get view tree state for %s', self.model_name) self.tree_states[parent][view.children_field] = ( expanded_nodes, selected_nodes) if view.view_type == 'tree': view.expand_nodes(expanded_nodes) view.select_nodes(selected_nodes) else: if selected_nodes and not self.current_record: record = None for node in selected_nodes[0]: new_record = self.group.get(node) if node < 0 and -node < len(self.group): # Negative id is the index of the new record new_record = self.group[-node] if not new_record: break else: record = new_record if record and record != self.current_record: self.current_record = record # Force a display of the view to synchronize the # widgets with the new record view.display() self.tree_states_done.add(id(view)) def save_tree_state(self, store=True): parent = self.parent.id if self.parent else None for view in self.views: if view.view_type == 'form': for widgets in view.widgets.values(): for widget in widgets: if hasattr(widget, 'screen'): widget.screen.save_tree_state(store) if view == self.current_view and view.view_type == 'form': if self.current_record: path = self.current_record.id if path < 0: path = -self.current_record.group.index( self.current_record) self.tree_states[parent][view.children_field] = ( [], [[path]]) elif view.view_type in {'tree', 'list-form'}: if view.view_type == 'tree': view.save_width() paths = view.get_expanded_paths() else: paths = [] selected_paths = view.get_selected_paths() if view == self.current_view: self.tree_states[parent][view.children_field] = ( paths, selected_paths) if (store and int(view.attributes.get('tree_state', False)) and CONFIG['client.save_tree_state']): json_domain = self.get_tree_domain(parent) json_paths = json.dumps(paths, separators=(',', ':')) json_selected_path = json.dumps( selected_paths, separators=(',', ':')) try: RPCExecute('model', 'ir.ui.view_tree_state', 'set', self.model_name, json_domain, view.children_field, json_paths, json_selected_path, process_exception=False) clear_cache('model.ir.ui.view_tree_state.get') except Exception: logger.warn( _('Unable to set view tree state'), exc_info=True) def get_tree_domain(self, parent): if parent: domain = (self.domain + [(self.exclude_field, '=', parent)]) else: domain = self.domain json_domain = json.dumps( domain, cls=JSONEncoder, separators=(',', ':')) return json_domain def load(self, ids, set_cursor=True, modified=False, position=-1): self.group.load(ids, modified=modified, position=position) if self.current_view: self.current_view.reset() self.current_record = None self.display(set_cursor=set_cursor) def display(self, set_cursor=False): if self.views and self.current_view: self.search_active(self.current_view.view_type in ('tree', 'graph', 'calendar')) for view in self.views: # Always display tree view to update model # because view can be used even if it is not shown # like for save_tree_state if (view == self.current_view or view.view_type == 'tree' or view.widget.get_parent()): view.display() self.current_view.widget.set_sensitive( bool(self.group or (self.current_view.view_type != 'form') or self.current_record)) if self.current_view.view_type == 'tree': view_tree = self.fields_view_tree.get( self.current_view.view_id, {}) if 'active' in view_tree['fields']: self.screen_container.but_active.show() else: self.screen_container.but_active.hide() else: self.screen_container.but_active.hide() if set_cursor: self.set_cursor(reset_view=False) self.set_tree_state() # Force record_message self.current_record = self.current_record def _get_next_record(self, test=False): view = self.current_view if (view and view.view_type in {'tree', 'form'} and self.current_record and self.current_record.group): group = self.current_record.group record = self.current_record while group: children = record.children_group(view.children_field) if children: record = children[0] break idx = group.index(record) + 1 if idx < len(group): record = group[idx] break parent = record.parent if not parent or record.model_name != parent.model_name: break next = parent.next.get(id(parent.group)) while not next: parent = parent.parent if not parent: break next = parent.next.get(id(parent.group)) if not next: break record = next break return record elif (view and view.view_type == 'list-form' and len(self.group) and self.current_record in self.group): idx = self.group.index(self.current_record) if 0 <= idx < len(self.group) - 1: return self.group[idx + 1] elif view and view.view_type == 'calendar': record = self.current_record goocalendar = view.widgets.get('goocalendar') if goocalendar: date = goocalendar.selected_date year = date.year month = date.month start = datetime.datetime(year, month, 1) nb_days = calendar.monthrange(year, month)[1] delta = datetime.timedelta(days=nb_days) end = start + delta events = goocalendar.event_store.get_events(start, end) events.sort() if not record: if events: return events[0].record else: return else: for idx, event in enumerate(events): if event.record == record: next_id = idx + 1 if next_id < len(events): return events[next_id].record break else: return self.group[0] if len(self.group) else None def has_next(self): next_record = self._get_next_record(test=True) return next_record and next_record != self.current_record def display_next(self): view = self.current_view if view: view.set_value() self.set_cursor(reset_view=False) self.current_record = self._get_next_record() self.set_cursor(reset_view=False) if view: view.display() def _get_prev_record(self, test=False): view = self.current_view if (view and view.view_type in {'tree', 'form'} and self.current_record and self.current_record.group): group = self.current_record.group record = self.current_record idx = group.index(record) - 1 if idx >= 0: record = group[idx] children = True while children: children = record.children_group(view.children_field) if children: record = children[-1] else: parent = record.parent if parent and record.model_name == parent.model_name: record = parent return record elif view and view.view_type == 'calendar': record = self.current_record goocalendar = view.widgets.get('goocalendar') if goocalendar: date = goocalendar.selected_date year = date.year month = date.month start = datetime.datetime(year, month, 1) nb_days = calendar.monthrange(year, month)[1] delta = datetime.timedelta(days=nb_days) end = start + delta events = goocalendar.event_store.get_events(start, end) events.sort() if not record: if events: return events[0].record else: return else: for idx, event in enumerate(events): if event.record == record: prev_id = idx - 1 if prev_id >= 0: return events[prev_id].record break elif (view and view.view_type == 'list-form' and len(self.group) and self.current_record in self.group): idx = self.group.index(self.current_record) if 0 < idx <= len(self.group) - 1: return self.group[idx - 1] else: return self.group[-1] if len(self.group) else None def has_prev(self): prev_record = self._get_prev_record(test=True) return prev_record and prev_record != self.current_record def display_prev(self): view = self.current_view if view: view.set_value() self.set_cursor(reset_view=False) self.current_record = self._get_prev_record() self.set_cursor(reset_view=False) if view: view.display() def invalid_message(self, record=None): if record is None: record = self.current_record domain_string = _('"%s" is not valid according to its domain.') domain_parser = DomainParser( {n: f.attrs for n, f in record.group.fields.items()}) fields = [] for field, invalid in sorted(record.invalid_fields.items()): string = record.group.fields[field].attrs['string'] if invalid == 'required' or invalid == [[field, '!=', None]]: fields.append(_('"%s" is required.') % string) elif invalid == 'domain': fields.append(domain_string % string) elif invalid == 'children': fields.append(_('The values of "%s" are not valid.') % string) else: if domain_parser.stringable(invalid): fields.append(domain_parser.string(invalid)) else: fields.append(domain_string % string) if len(fields) > 5: fields = fields[:5] + ['...'] return '\n'.join(fields) @property def selected_records(self): return self.current_view.selected_records if self.current_view else [] @property def selected_paths(self): if self.current_view and self.current_view.view_type == 'tree': return self.current_view.get_selected_paths() @property def listed_records(self): if (self.current_view and self.current_view.view_type in { 'tree', 'calendar', 'list-form'}): return self.current_view.listed_records elif self.current_record: return [self.current_record] else: return [] @property def listed_paths(self): if self.current_view and self.current_view.view_type == 'tree': return self.current_view.get_listed_paths() def clear(self): self.current_record = None self.group.clear() self.tree_states_done.clear() for view in self.views: view.reset() def on_change(self, fieldname, attr): self.current_record.on_change(fieldname, attr) self.display() def get_buttons(self): 'Return active buttons for the current view' def is_active(record, button): if button.attrs.get('type', 'class') == 'instance': return False states = record.expr_eval(button.attrs.get('states', {})) return not (states.get('invisible') or states.get('readonly')) if not self.selected_records: return [] buttons = self.current_view.get_buttons() if self.current_view else [] for record in self.selected_records: buttons = [b for b in buttons if is_active(record, b)] if not buttons: break return buttons def button(self, button): 'Execute button on the selected records' if self.current_view: self.current_view.set_value() fields = self.current_view.get_fields() for record in self.selected_records: domain = record.expr_eval( button.get('states', {})).get('pre_validate', []) if not record.validate(fields, pre_validate=domain): warning(self.invalid_message(record), _('Pre-validation')) self.display(set_cursor=True) if domain: # Reset valid state with normal domain record.validate(fields) return if button.get('confirm', False) and not sur(button['confirm']): return if button.get('type', 'class') == 'class': record_id = self.current_record.save(force_reload=False) if record_id is False or record_id < 0: return if button.get('type', 'class') == 'class': self._button_class(button) else: self._button_instance(button) def _button_instance(self, button): record = self.current_record args = record.expr_eval(button.get('change', [])) values = record._get_on_change_args(args) try: changes = RPCExecute('model', self.model_name, button['name'], values, context=self.context) except RPCException: return record.set_on_change(changes) record.set_modified() def _button_class(self, button): ids = [r.id for r in self.selected_records] context = self.context context['_timestamp'] = {} for record in self.selected_records: context['_timestamp'].update(record.get_timestamp()) try: action = RPCExecute('model', self.model_name, button['name'], ids, context=context) except RPCException: action = None self.reload(ids, written=True) self.record_saved() if isinstance(action, str): self.client_action(action) elif action: Action.execute(action, { 'model': self.model_name, 'id': self.current_record.id, 'ids': ids, }, context=self.context, keyword=True) def client_action(self, action): access = MODELACCESS[self.model_name] if action == 'new': if access['create']: self.new() elif action == 'delete': if (access['delete'] and (self.current_record.deletable if self.current_record else True)): self.remove(delete=not self.parent, force_remove=not self.parent) elif action == 'remove': if access['write'] and access['read'] and self.parent: self.remove(remove=True) elif action == 'copy': if access['create']: self.copy() elif action == 'next': self.display_next() elif action == 'previous': self.display_prev() elif action == 'close': from tryton.gui import Main Main().sig_win_close() elif action.startswith('switch'): self.switch_view(*action.split(None, 2)[1:]) elif action == 'reload': if (self.current_view and self.current_view.view_type in [ 'tree', 'graph', 'calendar'] and not self.parent): self.search_filter() elif action == 'reload menu': from tryton.gui import Main RPCContextReload(Main().sig_win_menu) elif action == 'reload context': RPCContextReload() def get_url(self, name=''): query_string = [] if self.domain: query_string.append(('domain', json.dumps( self.domain, cls=JSONEncoder, separators=(',', ':')))) context = self.local_context # Avoid rpc context if context: query_string.append(('context', json.dumps( context, cls=JSONEncoder, separators=(',', ':')))) if self.context_screen: query_string.append( ('context_model', self.context_screen.model_name)) if name: query_string.append( ('name', json.dumps(name, separators=(',', ':')))) if self.screen_container.tab_domain: query_string.append(('tab_domain', json.dumps( self.screen_container.tab_domain, cls=JSONEncoder, separators=(',', ':')))) path = [CONFIG['login.db'], 'model', self.model_name] view_ids = [v.view_id for v in self.views] + self.view_ids if self.current_view and self.current_view.view_type != 'form': if self.search_value: search_value = self.search_value else: search_string = self.screen_container.get_text() search_value = self.domain_parser.parse(search_string) if search_value: query_string.append(('search_value', json.dumps( search_value, cls=JSONEncoder, separators=(',', ':')))) elif self.current_record and self.current_record.id > -1: path.append(str(self.current_record.id)) if self.current_view: i = view_ids.index(self.current_view.view_id) view_ids = view_ids[i:] + view_ids[:i] if view_ids: query_string.append(('views', json.dumps( view_ids, separators=(',', ':')))) query_string = urllib.parse.urlencode(query_string) return urllib.parse.urlunparse(('tryton', CONFIG['login.host'], '/'.join(path), query_string, '', '')) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1745680285.2174447 tryton-7.0.24/tryton/gui/window/view_form/view/0000755000175000017500000000000015003173635017657 5ustar00cedced././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685599.0 tryton-7.0.24/tryton/gui/window/view_form/view/__init__.py0000644000175000017500000001044214517761237022003 0ustar00cedced# This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. import gettext from collections import defaultdict from tryton.common import node_attributes _ = gettext.gettext list_ = list # list builtins is overridden by import .list class View(object): view_type = None widget = None mnemonic_widget = None view_id = None modified = None editable = None creatable = None children_field = None scroll = None xml_parser = None def __init__(self, view_id, screen, xml): self.view_id = view_id self.screen = screen self.widgets = defaultdict(list_) self.state_widgets = [] self.attributes = node_attributes(xml) screen.set_on_write(self.attributes.get('on_write')) if self.xml_parser: self.xml_parser( self, self.screen.exclude_field, {k: f.attrs for k, f in self.screen.group.fields.items()} ).parse(xml) def set_value(self): raise NotImplementedError def get_fields(self): raise NotImplementedError @property def record(self): return self.screen.current_record @record.setter def record(self, value): self.screen.current_record = value @property def group(self): return self.screen.group @property def selected_records(self): return [] def get_buttons(self): raise NotImplementedError @staticmethod def parse(screen, view_id, view_type, xml, children_field): from .calendar_ import ViewCalendar from .form import ViewForm from .graph import ViewGraph from .list import ViewTree from .list_form import ViewListForm root, = xml.childNodes if view_type == 'tree': return ViewTree(view_id, screen, root, children_field) elif view_type == 'form': return ViewForm(view_id, screen, root) elif view_type == 'graph': return ViewGraph(view_id, screen, root) elif view_type == 'calendar': return ViewCalendar(view_id, screen, root) elif view_type == 'list-form': return ViewListForm(view_id, screen, root) class XMLViewParser: def __init__(self, view, exclude_field, field_attrs): self.view = view self.exclude_field = exclude_field self.field_attrs = field_attrs def _node_attributes(self, node): node_attrs = node_attributes(node) if 'name' in node_attrs: field = self.field_attrs.get(node_attrs['name'], {}) else: field = {} for name in ['readonly', 'homogeneous']: if name in node_attrs: node_attrs[name] = bool(int(node_attrs[name])) for name in [ 'yexpand', 'yfill', 'xexpand', 'xfill', 'colspan', 'position', 'height', 'width', 'expand']: if name in node_attrs: node_attrs[name] = int(node_attrs[name]) for name in ['xalign', 'yalign']: if name in node_attrs: node_attrs[name] = float(node_attrs[name]) if field: node_attrs.setdefault('widget', field['type']) if node.tagName == 'label' and 'string' not in node_attrs: node_attrs['string'] = field['string'] + _(':') if node.tagName == 'field' and 'help' not in node_attrs: node_attrs['help'] = field['help'] for name in [ 'relation', 'domain', 'selection', 'string', 'states', 'relation_field', 'views', 'invisible', 'add_remove', 'sort', 'context', 'size', 'filename', 'autocomplete', 'translate', 'create', 'delete', 'selection_change_with', 'schema_model', 'required', 'help_selection', 'help_field', 'order', 'symbol', 'monetary']: if name in field: node_attrs.setdefault(name, field[name]) return node_attrs def parse(self, node): node_attrs = self._node_attributes(node) if node.nodeType != node.ELEMENT_NODE: return parser = getattr(self, '_parse_%s' % node.tagName) parser(node, node_attrs) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685599.0 tryton-7.0.24/tryton/gui/window/view_form/view/calendar_.py0000644000175000017500000001367614517761237022170 0ustar00cedced# This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. import datetime import gettext from functools import wraps from gi.repository import Gtk from . import View, XMLViewParser try: from .calendar_gtk.calendar_ import Calendar_ from .calendar_gtk.toolbar import Toolbar except ImportError: Calendar_ = None Toolbar = None from tryton.common import MODELACCESS _ = gettext.gettext def goocalendar_required(func): "Decorator for goocalendar required" @wraps(func) def wrapper(self, *args, **kwargs): if 'goocalendar' not in self.widgets: return return func(self, *args, **kwargs) return wrapper class CalendarXMLViewParser(XMLViewParser): def __init__(self, view, exclude_field, field_attrs): super().__init__(view, exclude_field, field_attrs) self.calendar_fields = [] def _parse_calendar(self, node, attributes): for child in node.childNodes: self.parse(child) goocalendar = Calendar_( self.view.attributes, self.view, self.calendar_fields) toolbar = Toolbar(goocalendar) self.view.widgets['goocalendar'] = goocalendar self.view.widgets['toolbar'] = toolbar def _parse_field(self, node, attributes): self.calendar_fields.append(attributes) class ViewCalendar(View): editable = False creatable = False view_type = 'calendar' xml_parser = CalendarXMLViewParser def __init__(self, view_id, screen, xml): self.widget = Gtk.VBox() if not Calendar_: self.widgets = {} return super().__init__(view_id, screen, xml) goocalendar = self.widgets['goocalendar'] toolbar = self.widgets['toolbar'] goocalendar.connect('view-changed', self.on_view_changed, toolbar) goocalendar.connect('page-changed', self.on_page_changed, toolbar) goocalendar.connect('event-pressed', self.on_event_pressed) goocalendar.connect('event-activated', self.on_event_activated) goocalendar.connect('event-released', self.on_event_released) goocalendar.connect('day-pressed', self.on_day_pressed) goocalendar.connect('day-activated', self.on_day_activated) self.widget.pack_start(toolbar, expand=False, fill=False, padding=0) vp = Gtk.Viewport() vp.set_shadow_type(Gtk.ShadowType.NONE) vp.add(goocalendar) self.scroll = scroll = Gtk.ScrolledWindow() scroll.add(vp) scroll.set_policy( Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC) scroll.set_placement(Gtk.CornerType.TOP_LEFT) viewport = Gtk.Viewport() viewport.set_shadow_type(Gtk.ShadowType.ETCHED_IN) viewport.add(scroll) self.widget.pack_start(viewport, expand=True, fill=True, padding=0) if self.attributes.get('height') or self.attributes.get('width'): scroll.set_size_request( int(self.attributes.get('width', -1)), int(self.attributes.get('height', -1))) def on_page_changed(self, goocalendar, day, toolbar): toolbar.update_displayed_date() if goocalendar.update_domain(): self.screen.search_filter() def on_view_changed(self, goocalendar, view, toolbar): toolbar.update_displayed_date() if goocalendar.update_domain(): self.screen.search_filter() def on_event_pressed(self, goocalendar, event): self.record = event.record def on_event_activated(self, goocalendar, event): self.screen.switch_view('form') def on_event_released(self, goocalendar, event): dtstart = self.attributes['dtstart'] dtend = self.attributes.get('dtend') record = event.record group = record.group previous_start = record[dtstart].get(record) new_start = event.start new_end = event.end if not isinstance(previous_start, datetime.datetime): new_start = event.start.date() new_end = event.end.date() if event.end else None if previous_start <= new_start: if dtend: group.fields[dtend].set_client(record, new_end) group.fields[dtstart].set_client(record, new_start) else: group.fields[dtstart].set_client(record, new_start) if dtend: group.fields[dtend].set_client(record, new_end) goocalendar.select(new_start) record.save() def on_day_pressed(self, goocalendar, day): self.record = None def on_day_activated(self, goocalendar, day): model_access = MODELACCESS[self.screen.model_name] if (bool(int(self.attributes.get('editable', 1))) and model_access['create']): self.screen.new() def __getitem__(self, name): return None @goocalendar_required def destroy(self): self.widget.destroy() self.widgets['goocalendar'].destroy() @goocalendar_required def get_selected_date(self): return self.widgets['goocalendar'].selected_date @goocalendar_required def set_default_date(self, record, selected_date): self.widgets['goocalendar'].set_default_date(record, selected_date) def current_domain(self): if 'goocalendar' in self.widgets: return self.widgets['goocalendar'].current_domain() else: # No need to load any record as nothing will be shown return [('id', '=', -1)] def set_value(self): pass def reset(self): pass @goocalendar_required def display(self): self.widgets['goocalendar'].display(self.group) def set_cursor(self, new=False, reset_view=True): pass def get_fields(self): return [] @property def listed_records(self): event_store = self.widgets['goocalendar'].event_store return [e.record for e in event_store.get_events()] ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1745680285.2174447 tryton-7.0.24/tryton/gui/window/view_form/view/calendar_gtk/0000755000175000017500000000000015003173635022275 5ustar00cedced././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685599.0 tryton-7.0.24/tryton/gui/window/view_form/view/calendar_gtk/__init__.py0000644000175000017500000000022014517761237024412 0ustar00cedced# This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1725720314.0 tryton-7.0.24/tryton/gui/window/view_form/view/calendar_gtk/calendar_.py0000644000175000017500000001316614667063372024601 0ustar00cedced# This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. import calendar import datetime import goocalendar from tryton.common import MODELACCESS from tryton.config import CONFIG from .dates_period import DatesPeriod _colors = CONFIG['calendar.colors'].split(',') class Calendar_(goocalendar.Calendar): 'Calendar' def __init__(self, attrs, view, fields, event_store=None): super(Calendar_, self).__init__( event_store, attrs.get('mode', 'month')) self.props.selected_border_color = _colors[1] if hasattr(self.props, 'selected_text_color'): self.props.selected_text_color = _colors[0] self.attrs = attrs self.view_calendar = view self.fields = fields self.event_store = event_store self.current_domain_period = self.get_displayed_period() def set_default_date(self, record, selected_date): dtstart = self.attrs['dtstart'] record[dtstart].set(record, datetime.datetime.combine(selected_date, datetime.time(0))) record.on_change([dtstart]) record.on_change_with([dtstart]) def get_displayed_period(self): cal = calendar.Calendar(self.firstweekday) if self.view == 'day': first_date = self.selected_date last_date = self.selected_date + datetime.timedelta(1) if self.view == 'week': week = goocalendar.util.my_weekdatescalendar(cal, self.selected_date) first_date = week[0] last_date = week[6] last_date += datetime.timedelta(1) elif self.view == 'month': weeks = goocalendar.util.my_monthdatescalendar(cal, self.selected_date) first_date = weeks[0][0] last_date = weeks[5][6] last_date += datetime.timedelta(1) displayed_period = DatesPeriod(first_date, last_date) return displayed_period def update_domain(self): displayed_period = self.get_displayed_period() if not displayed_period.is_in(self.current_domain_period): self.current_domain_period = displayed_period return True return False def current_domain(self): first_datetime, last_datetime = \ self.current_domain_period.get_dates(True) dtstart = self.attrs['dtstart'] dtend = self.attrs.get('dtend') or dtstart domain = [ (dtstart, '!=', None), (dtend, '!=', None), ['OR', ['AND', (dtstart, '>=', first_datetime), (dtstart, '<', last_datetime)], ['AND', (dtend, '>=', first_datetime), (dtend, '<', last_datetime)], ['AND', (dtstart, '<', first_datetime), (dtend, '>', last_datetime)], ], ] return domain def get_colors(self, record): text_color = _colors[0] if self.attrs.get('color'): text_color = record[self.attrs['color']].get(record) bg_color = _colors[1] if self.attrs.get('background_color'): bg_color = record[self.attrs['background_color']].get( record) return text_color, bg_color def display(self, group): def is_date_only(value): return (isinstance(value, datetime.date) and not isinstance(value, datetime.datetime)) dtstart = self.attrs['dtstart'] dtend = self.attrs.get('dtend') if self.view_calendar.record: record = self.view_calendar.record date = record[dtstart].get(record) if date: # select the day of the current record self.select(date) event_store = goocalendar.EventStore() model_access = MODELACCESS[self.view_calendar.screen.model_name] editable = ( bool(int(self.view_calendar.attributes.get('editable', 1))) and model_access['write']) for record in group: if not record[dtstart].get(record): continue start = record[dtstart].get_client(record) record[dtstart].state_set(record) if dtend: end = record[dtend].get_client(record) record[dtend].state_set(record) else: end = None midnight = datetime.time(0) all_day = is_date_only(start) and (not end or is_date_only(end)) if not isinstance(start, datetime.datetime): start = datetime.datetime.combine(start, midnight) if end and not isinstance(end, datetime.datetime): end = datetime.datetime.combine(end, midnight) # Skip invalid event if end is not None and start > end: continue text_color, bg_color = self.get_colors(record) label = '\n'.join(record[attrs['name']].get_client(record) for attrs in self.fields).rstrip() event_editable = ( editable and not record[dtstart].get_state_attrs(record).get( 'readonly', False) and (not dtend or not record[dtend].get_state_attrs(record).get( 'readonly', False))) event = goocalendar.Event(label, start, end, text_color=text_color, bg_color=bg_color, all_day=all_day, editable=event_editable) event.record = record event_store.add(event) self.event_store = event_store self.grab_focus(self.get_root_item()) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685599.0 tryton-7.0.24/tryton/gui/window/view_form/view/calendar_gtk/dates_period.py0000644000175000017500000000163614517761237025331 0ustar00cedced# This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. import datetime class DatesPeriod(): """ This class represents a period of time between two dates or two datetimes. """ def __init__(self, start, end): assert type(start) is type(end) self.start = start self.end = end def is_in(self, period): return self.start >= period.start and self.end <= period.end def get_dates(self, format_datetime=False): if not format_datetime: return self.start, self.end midnight = datetime.time(0) start = datetime.datetime.combine(self.start, midnight) end = datetime.datetime.combine(self.end, midnight) return start, end def __str__(self): string = self.start.__str__() + ' => ' + self.end.__str__() return string ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685599.0 tryton-7.0.24/tryton/gui/window/view_form/view/calendar_gtk/toolbar.py0000644000175000017500000002306514517761237024331 0ustar00cedced# This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. import calendar import datetime import gettext from gi.repository import Gdk, Gtk from tryton.common import IconFactory from tryton.common.datetime_ import popup_hide, popup_position, popup_show from tryton.gui import Main _ = gettext.gettext class Toolbar(Gtk.Toolbar): def __init__(self, goocalendar): super(Toolbar, self).__init__() self.goocalendar = goocalendar self.accel_group = Main().accel_group today_button = Gtk.ToolButton() today_button.set_label(_('Today')) today_button.set_homogeneous(False) today_button.connect("clicked", self.on_today_button_clicked) today_button.add_accelerator( "clicked", self.accel_group, Gdk.KEY_t, Gdk.ModifierType.MODIFIER_MASK, Gtk.AccelFlags.VISIBLE) self.insert(today_button, -1) arrow_left = IconFactory.get_image('tryton-arrow-left') go_back = Gtk.ToolButton() go_back.set_icon_widget(arrow_left) go_back.set_label(_("go back")) go_back.set_expand(False) go_back.set_homogeneous(False) go_back.connect("clicked", self.on_go_back_clicked) self.insert(go_back, -1) self.current_page_label = Gtk.Label( width_chars=10, max_width_chars=10, ellipsize=True) self.current_page = Gtk.ToggleToolButton() self.current_page.set_label_widget(self.current_page_label) self.current_page.connect("clicked", self.on_current_page_clicked) self.insert(self.current_page, -1) self.__cal_popup = Gtk.Window(type=Gtk.WindowType.POPUP) self.__cal_popup.set_events( self.__cal_popup.get_events() | Gdk.EventMask.KEY_PRESS_MASK) self.__cal_popup.set_resizable(False) self.__cal_popup.connect('delete-event', self.on_cal_popup_closed) self.__cal_popup.connect( 'key-press-event', self.on_cal_popup_key_pressed) self.__cal_popup.connect( 'button-press-event', self.on_cal_popup_button_pressed) gtkcal = Gtk.Calendar() gtkcal.connect('day-selected', self.on_gtkcal_day_selected) gtkcal.connect( 'day-selected-double-click', self.on_gtkcal_day_selected_double_click) gtkcal.set_display_options( Gtk.CalendarDisplayOptions.SHOW_HEADING | Gtk.CalendarDisplayOptions.SHOW_WEEK_NUMBERS | Gtk.CalendarDisplayOptions.SHOW_DAY_NAMES) gtkcal.set_no_show_all(True) self.__cal_popup.add(gtkcal) gtkcal.show() self.gtkcal = gtkcal self.goocalendar.connect('day-selected', self.on_goocalendar_day_selected) arrow_right = IconFactory.get_image('tryton-arrow-right') go_forward = Gtk.ToolButton() go_forward.set_icon_widget(arrow_right) go_forward.set_label(_("go forward")) go_forward.set_expand(False) go_forward.set_homogeneous(False) go_forward.connect("clicked", self.on_go_forward_clicked) self.insert(go_forward, -1) arrow_left = IconFactory.get_image('tryton-arrow-left') previous_year = Gtk.ToolButton() previous_year.set_icon_widget(arrow_left) previous_year.set_label(_("previous year")) previous_year.set_expand(False) previous_year.set_homogeneous(False) previous_year.connect("clicked", self.on_previous_year_clicked) self.insert(previous_year, -1) self.current_year_label = Gtk.Label(width_chars=4) current_year = Gtk.ToolItem() current_year.add(self.current_year_label) self.insert(current_year, -1) arrow_right = IconFactory.get_image('tryton-arrow-right') next_year = Gtk.ToolButton() next_year.set_icon_widget(arrow_right) next_year.set_label(_("next year")) next_year.set_expand(False) next_year.set_homogeneous(False) next_year.connect("clicked", self.on_next_year_clicked) self.insert(next_year, -1) blank_widget = Gtk.ToolItem() blank_widget.set_expand(True) self.insert(blank_widget, -1) day_button = Gtk.RadioToolButton() day_button.set_label(_("Day")) day_button.connect("clicked", self.on_day_button_clicked) day_button.add_accelerator( "clicked", self.accel_group, Gdk.KEY_d, Gdk.ModifierType.MODIFIER_MASK, Gtk.AccelFlags.VISIBLE) week_button = Gtk.RadioToolButton.new_from_widget(day_button) week_button.set_label(_("Week")) week_button.connect("clicked", self.on_week_button_clicked) week_button.add_accelerator( "clicked", self.accel_group, Gdk.KEY_w, Gdk.ModifierType.MODIFIER_MASK, Gtk.AccelFlags.VISIBLE) month_button = Gtk.RadioToolButton.new_from_widget(week_button) month_button.set_label_widget(Gtk.Label(label=_("Month"))) month_button.connect("clicked", self.on_month_button_clicked) month_button.add_accelerator( "clicked", self.accel_group, Gdk.KEY_m, Gdk.ModifierType.MODIFIER_MASK, Gtk.AccelFlags.VISIBLE) self.insert(month_button, -1) self.insert(week_button, -1) self.insert(day_button, -1) buttons = { 'month': month_button, 'week': week_button, 'day': day_button, } buttons[self.goocalendar.view].set_active(True) self.update_displayed_date() self.set_style(Gtk.ToolbarStyle.ICONS) def update_displayed_date(self): date = self.goocalendar.selected_date year = date.timetuple()[0] month = date.timetuple()[1] day = date.timetuple()[2] month_label = calendar.month_name[month] self.current_year_label.set_text(str(year)) if self.goocalendar.view == "month": self.current_page_label.set_text(month_label) elif self.goocalendar.view == "week": week_number = datetime.date(year, month, day).isocalendar()[1] new_label = _('Week') + ' ' + str(week_number) new_label += ' (' + month_label + ')' self.current_page_label.set_text(new_label) elif self.goocalendar.view == "day": new_label = date.strftime('%x') self.current_page_label.set_text(new_label) def on_today_button_clicked(self, widget): self.goocalendar.select(datetime.date.today()) def on_go_back_clicked(self, widget): self.goocalendar.previous_page() def on_current_page_clicked(self, widget): if widget.get_active(): self.__cal_popup.set_transient_for(widget.get_toplevel()) popup_position(widget, self.__cal_popup) popup_show(self.__cal_popup) def on_cal_popup_closed(self, widget): self.cal_popup_hide() return True def on_cal_popup_key_pressed(self, widget, event): if event.keyval != Gdk.KEY_Escape: return False widget.stop_emission_by_name('key-press-event') self.cal_popup_hide() return True def on_cal_popup_button_pressed(self, widget, event): child = event.window if child != widget.props.window: while child: if child == widget.props.window: return False child = child.get_parent() self.cal_popup_hide() return True def cal_popup_hide(self): popup_hide(self.__cal_popup) self.current_page.set_active(False) def on_gtkcal_day_selected(self, gtkcal): year, month, day = gtkcal.get_date() month += 1 # months go from 1 to 12 instead of from 0 to 11 self.goocalendar.select(datetime.date(year, month, day)) def on_gtkcal_day_selected_double_click(self, gtkcal): self.cal_popup_hide() def on_goocalendar_day_selected(self, goocalendar, day): # months go from 0 to 11 in Gtk.Calendar instead of 1 to 12 new_date = self.goocalendar.selected_date self.gtkcal.select_month(new_date.month - 1, new_date.year) self.gtkcal.handler_block_by_func(self.on_gtkcal_day_selected) self.gtkcal.select_day(new_date.day) self.gtkcal.handler_unblock_by_func(self.on_gtkcal_day_selected) def on_go_forward_clicked(self, widget): self.goocalendar.next_page() def on_previous_year_clicked(self, widget): date = datetime.datetime.combine(self.goocalendar.selected_date, datetime.time(0)) year, month, day = date.timetuple()[:3] year -= 1 cal = calendar.Calendar(self.goocalendar.firstweekday) next_month_days = [d for d in cal.itermonthdays(year, month)] if day not in next_month_days: day = max(next_month_days) self.goocalendar.select(datetime.datetime(year, month, day)) def on_next_year_clicked(self, widget): date = datetime.datetime.combine(self.goocalendar.selected_date, datetime.time(0)) year, month, day = date.timetuple()[:3] year += 1 cal = calendar.Calendar(self.goocalendar.firstweekday) next_month_days = [d for d in cal.itermonthdays(year, month)] if day not in next_month_days: day = max(next_month_days) self.goocalendar.select(datetime.datetime(year, month, day)) def on_day_button_clicked(self, widget): self.goocalendar.set_view("day") def on_week_button_clicked(self, widget): self.goocalendar.set_view("week") def on_month_button_clicked(self, widget): self.goocalendar.set_view("month") ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1734197481.0 tryton-7.0.24/tryton/gui/window/view_form/view/form.py0000644000175000017500000005500614727340351021205 0ustar00cedced# This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. import gettext import operator from gi.repository import GLib, Gtk from tryton.common import IconFactory, Tooltips, get_align, node_attributes from tryton.common.button import Button from tryton.common.focus import ( find_first_focus_widget, find_focusable_child, find_focused_child, get_invisible_ancestor, next_focus_widget) from tryton.common.underline import set_underline from tryton.config import CONFIG from tryton.gui.window.code_scanner import CodeScanner from . import View, XMLViewParser from .form_gtk.binary import Binary from .form_gtk.calendar_ import Date, DateTime, Time from .form_gtk.char import Char, Password from .form_gtk.checkbox import CheckBox from .form_gtk.dictionary import DictWidget from .form_gtk.document import Document from .form_gtk.float import Float from .form_gtk.image import Image as Image2 from .form_gtk.integer import Integer from .form_gtk.many2many import Many2Many from .form_gtk.many2one import Many2One from .form_gtk.multiselection import MultiSelection from .form_gtk.one2many import One2Many from .form_gtk.one2one import One2One from .form_gtk.progressbar import ProgressBar from .form_gtk.pyson import PYSON from .form_gtk.reference import Reference from .form_gtk.richtextbox import RichTextBox from .form_gtk.selection import Selection from .form_gtk.state_widget import ( Expander, Frame, Image, Label, Link, Notebook, ScrolledWindow, VBox) from .form_gtk.textbox import TextBox from .form_gtk.timedelta import TimeDelta from .form_gtk.url import HTML, SIP, URL, CallTo, Email _ = gettext.gettext class _Container(object): def __init__(self, col=4, homogeneous=False): super().__init__() if col < 0: col = 0 self.col = col self.tooltips = Tooltips() self.tooltips.enable() def add_row(self): raise NotImplementedError def add_col(self): raise NotImplementedError def add(self, widget, attributes): if widget and attributes.get('help'): self.tooltips.set_tip(widget, attributes['help']) @staticmethod def constructor(col=4, homogeneous=False): if CONFIG['client.modepda']: col = 1 if col <= 0: return HContainer(col, homogeneous) elif col == 1: return VContainer(col, homogeneous) else: return Container(col, homogeneous) class Container(_Container): def __init__(self, col=4, homogeneous=False): super().__init__(col=col, homogeneous=homogeneous) self.container = Gtk.Grid( column_spacing=3, row_spacing=3, column_homogeneous=homogeneous, row_homogeneous=homogeneous, border_width=3) self.last = (0, 0) def add_row(self): height, width = self.last self.last = (height + 1, 0) def add_col(self): height, width = self.last self.last = (height, width + 1) def add(self, widget, attributes): super().add(widget, attributes) colspan = attributes.get('colspan', 1) if self.col > 0: height, width = self.last if colspan > self.col: colspan = self.col if width + colspan > self.col: self.add_row() else: self.add_col() height, width = self.last self.last = height, width + colspan if widget: widget.set_vexpand(bool(attributes.get('yexpand'))) widget.set_hexpand(bool(attributes.get('xexpand', True))) widget.show_all() self.container.attach(widget, width, height, colspan, 1) class VContainer(_Container): def __init__(self, col=1, homogeneous=False): col = 1 super().__init__(col=col, homogeneous=homogeneous) self.container = Gtk.VBox() self.container.set_homogeneous(homogeneous) def add_row(self): pass def add_col(self): pass def add(self, widget, attributes): super().add(widget, attributes) if widget: expand = bool(int(attributes.get('yexpand', False))) fill = bool(int(attributes.get('yfill', False))) self.container.pack_start( widget, expand=expand, fill=fill, padding=2) class HContainer(_Container): def __init__(self, col=0, homogeneous=False): col = 0 super().__init__(col=col, homogeneous=homogeneous) self.container = Gtk.HBox() self.container.set_homogeneous(homogeneous) def add_row(self): pass def add_col(self): pass def add(self, widget, attributes): super().add(widget, attributes) if widget: expand = bool(int(attributes.get('xexpand', True))) fill = bool(int(attributes.get('xfill', True))) self.container.pack_start( widget, expand=expand, fill=fill, padding=1) class FormXMLViewParser(XMLViewParser): WIDGETS = { 'binary': Binary, 'boolean': CheckBox, 'callto': CallTo, 'char': Char, 'date': Date, 'datetime': DateTime, 'dict': DictWidget, 'document': Document, 'email': Email, 'float': Float, 'html': HTML, 'image': Image2, 'integer': Integer, 'many2many': Many2Many, 'many2one': Many2One, 'multiselection': MultiSelection, 'numeric': Float, 'one2many': One2Many, 'one2one': One2One, 'password': Password, 'progressbar': ProgressBar, 'pyson': PYSON, 'reference': Reference, 'richtext': RichTextBox, 'selection': Selection, 'sip': SIP, 'text': TextBox, 'time': Time, 'timedelta': TimeDelta, 'timestamp': DateTime, 'url': URL, } def __init__(self, view, exclude_field, field_attrs): super().__init__(view, exclude_field, field_attrs) self._containers = [] self._mnemonics = {} @property def container(self): if self._containers: return self._containers[-1] return None def _parse_form(self, node, attributes): container_attributes = node_attributes(node) container = Container.constructor( int(container_attributes.get('col', 4)), container_attributes.get('homogeneous', False)) self.view.viewport.add(container.container) self.parse_child(node, container) assert not self._containers def parse_child(self, node, container=None): if container: self._containers.append(container) for child in node.childNodes: self.parse(child) if container: self._containers.pop() def _parse_field(self, node, attributes): name = attributes['name'] if name and name == self.exclude_field: self.container.add(None, attributes) return widget = self.WIDGETS[attributes['widget']](self.view, attributes) self.view.widgets[name].append(widget) if widget.expand: attributes.setdefault('yexpand', True) attributes.setdefault('yfill', True) if attributes.get('height') or attributes.get('width'): widget.widget.set_size_request( int(attributes.get('width', -1)), int(attributes.get('height', -1))) widget.widget.set_halign(get_align( attributes.get('xalign', 0.5), bool(attributes.get('xexpand', True)))) widget.widget.set_valign(get_align( attributes.get('yalign', 0.5), bool(attributes.get('yexpand')))) self.container.add(widget.widget, attributes) if name in self._mnemonics and widget.mnemonic_widget: label = self._mnemonics.pop(name) label.set_label(set_underline(label.get_label())) label.set_use_underline(True) label.set_mnemonic_widget(widget.mnemonic_widget) def _parse_button(self, node, attributes): button = Button(attributes) button.connect('clicked', self.view.button_clicked) self.view.state_widgets.append(button) self.container.add(button, attributes) def _parse_link(self, node, attributes): link = Link(attrs=attributes) self.view.state_widgets.append(link) self.container.add(link, attributes) def _parse_image(self, node, attributes): image = Image(attrs=attributes) self.view.state_widgets.append(image) self.container.add(image, attributes) def _parse_separator(self, node, attributes): name = attributes.get('name') if name and name == self.exclude_field: self.container.add(None, attributes) return vbox = VBox(attrs=attributes) if attributes.get('string'): attributes.setdefault('xexpand', 0) attributes.setdefault('xalign', 0) attributes.setdefault('yalign', 0.5) label = Label(label=attributes['string'], attrs=attributes) label.set_halign(get_align( attributes['xalign'], bool(attributes.get('xexpand')))) label.set_valign(get_align( attributes['yalign'], bool(attributes.get('yexpand')))) label.props.xalign = float(attributes['xalign']) label.props.yalign = float(attributes['yalign']) vbox.pack_start(label, expand=True, fill=True, padding=0) self.view.state_widgets.append(label) if name: self._mnemonics[name] = label vbox.pack_start(Gtk.HSeparator(), expand=True, fill=True, padding=0) self.view.state_widgets.append(vbox) self.container.add(vbox, attributes) def _parse_label(self, node, attributes): name = attributes.get('name') if name and name == self.exclude_field: self.container.add(None, attributes) return if CONFIG['client.modepda']: attributes['xalign'] = 0.0 attributes.setdefault('xexpand', 0) attributes.setdefault('xalign', 1) attributes.setdefault('yalign', 0.5) label = Label(label=attributes.get('string', ''), attrs=attributes) label.set_halign(get_align( attributes['xalign'], bool(attributes.get('xexpand')))) label.set_valign(get_align( attributes.get('yalign', 0.5), bool(attributes.get('yexpand')))) label.props.xalign = float(attributes['xalign']) label.props.yalign = float(attributes['yalign']) label.set_angle(int(attributes.get('angle', 0))) self.view.state_widgets.append(label) self.container.add(label, attributes) if name: self._mnemonics[name] = label def _parse_newline(self, node, attributes): self.container.add_row() def _parse_notebook(self, node, attributes): attributes.setdefault('yexpand', True) attributes.setdefault('yfill', True) attributes.setdefault('colspan', 4) notebook = Notebook(attrs=attributes) notebook.set_scrollable(True) notebook.set_border_width(3) if attributes.get('height') or attributes.get('width'): notebook.set_size_request( int(attributes.get('width', -1)), int(attributes.get('height', -1))) # Force to display the first time it switches on a page # This avoids glitch in position of widgets def switch(notebook, page, page_num): if not self.view.widget: # Not yet finish to parse return notebook.grab_focus() self.view.display() notebook.disconnect(handler_id) handler_id = notebook.connect('switch-page', switch) self.view.state_widgets.append(notebook) self.view.notebooks.append(notebook) self.container.add(notebook, attributes) self.parse_child(node, notebook) def _parse_page(self, node, attributes): tab_box = Gtk.HBox(spacing=3) if 'name' in attributes and attributes['name'] == self.exclude_field: return label = Gtk.Label(label=set_underline(attributes['string'])) label.set_use_underline(True) if 'icon' in attributes: tab_box.pack_start(IconFactory.get_image( attributes['icon'], Gtk.IconSize.SMALL_TOOLBAR), expand=True, fill=True, padding=0) tab_box.pack_start(label, expand=True, fill=True, padding=0) tab_box.show_all() viewport = Gtk.Viewport() viewport.set_shadow_type(Gtk.ShadowType.NONE) scrolledwindow = ScrolledWindow(attrs=attributes) scrolledwindow.set_shadow_type(Gtk.ShadowType.NONE) scrolledwindow.set_policy( Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC) scrolledwindow.add(viewport) scrolledwindow.show_all() self.view.state_widgets.append(scrolledwindow) self.container.append_page(scrolledwindow, tab_box) container = Container.constructor( int(attributes.get('col', 4)), attributes.get('homogeneous', False)) self.parse_child(node, container) viewport.add(container.container) def _parse_group(self, node, attributes): group = Container.constructor( int(attributes.get('col', 4)), attributes.get('homogeneous', False)) if 'name' in attributes and attributes['name'] == self.exclude_field: self.container.add(None, attributes) return can_expand = attributes.get('expandable') if can_expand: widget = Expander(label=attributes.get('string'), attrs=attributes) widget.add(group.container) widget.set_expanded(can_expand == '1') self.view.expandables.append(widget) else: widget = Frame(label=attributes.get('string'), attrs=attributes) widget.add(group.container) widget.set_halign(get_align( attributes.get('xalign', 0.5), bool(attributes.get('xexpand', True)))) widget.set_valign(get_align( attributes.get('yalign', 0.5), bool(attributes.get('yexpand')))) self.view.state_widgets.append(widget) self.container.add(widget, attributes) # Parse the children at the end to preserve the order of the state # widgets self.parse_child(node, group) def _parse_hpaned(self, node, attributes): self._parse_paned(node, attributes, Gtk.HPaned) def _parse_vpaned(self, node, attributes): self._parse_paned(node, attributes, Gtk.VPaned) def _parse_paned(self, node, attributes, Paned): attributes.setdefault('yexpand', True) attributes.setdefault('yfill', True) paned = Paned() if 'position' in attributes: paned.set_position(attributes['position']) self.container.add(paned, attributes) self.parse_child(node, paned) def _parse_child(self, node, attributes): paned = self.container container = Container.constructor( int(attributes.get('col', 4)), attributes.get('homogeneous', False)) self.parse_child(node, container) if not paned.get_child1(): pack = paned.pack1 else: pack = paned.pack2 pack(container.container, resize=True, shrink=True) class ViewForm(View): editable = True view_type = 'form' xml_parser = FormXMLViewParser def __init__(self, view_id, screen, xml): self.notebooks = [] self.expandables = [] vbox = Gtk.VBox() vp = Gtk.Viewport() vp.set_shadow_type(Gtk.ShadowType.NONE) self.scroll = scroll = Gtk.ScrolledWindow() scroll.add(vp) scroll.set_policy( Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC) scroll.set_placement(Gtk.CornerType.TOP_LEFT) viewport = Gtk.Viewport() viewport.set_shadow_type(Gtk.ShadowType.ETCHED_IN) viewport.add(scroll) vbox.pack_start(viewport, expand=True, fill=True, padding=0) self.widget = vbox self.viewport = vp super().__init__(view_id, screen, xml) self.creatable = bool(int(self.attributes.get('creatable', 1))) if self.attributes.get('scan_code'): code_scanner_btn = Button({ 'string': _("Scan"), 'icon': 'tryton-barcode-scanner', 'states': self.attributes.get('scan_code_states', {}), }) code_scanner_btn.set_image(IconFactory.get_image( 'tryton-barcode-scanner', Gtk.IconSize.BUTTON)) code_scanner_btn.set_always_show_image(True) code_scanner_btn.set_receives_default(False) vbox.pack_start( code_scanner_btn, expand=False, fill=True, padding=2) code_scanner_btn.connect( 'clicked', lambda *a: CodeScanner( self.on_scan_code, self.attributes['scan_code'] == 'loop')) def get_fields(self): return list(self.widgets.keys()) def __getitem__(self, name): return self.widgets[name][0] def destroy(self): for widget_name in list(self.widgets.keys()): for widget in self.widgets[widget_name]: widget.destroy() self.widget.destroy() def set_value(self, focused_widget=False): record = self.record if record: for name, widgets in self.widgets.items(): if name in record.group.fields: for widget in widgets: if (not focused_widget or widget.widget.is_focus() or (isinstance(widget.widget, Gtk.Container) and widget.widget.get_focus_child())): widget.set_value() @property def selected_records(self): if self.record: return [self.record] return [] @property def modified(self): return any(w.modified for widgets in self.widgets.values() for w in widgets) def get_buttons(self): return [b for b in self.state_widgets if isinstance(b, Button)] def reset(self): record = self.record if record: for name, widgets in self.widgets.items(): field = record.group.fields.get(name) if field and 'invalid' in field.get_state_attrs(record): for widget in widgets: field.get_state_attrs(record)['invalid'] = False widget.display() def display(self): record = self.record if record: # Force to set fields in record # Get first the lazy one from the view to reduce number of requests field_names = set(self.get_fields()) for name, field in record.group.fields.items(): if self.view_id in field.views: field_names.add(name) fields = [] for name in field_names: field = record.group.fields[name] fields.append( (name, field.attrs.get('loading', 'eager') == 'eager', len(field.views))) fields = sorted(fields, key=operator.itemgetter(1, 2)) for field, _, _ in fields: record[field].get(record) focused_widget = find_focused_child(self.widget) for name, widgets in self.widgets.items(): field = None if record: field = record.group.fields.get(name) if field: field.state_set(record) for widget in widgets: widget.display() for widget in self.state_widgets: widget.state_set(record) if focused_widget: invisible_ancestor = get_invisible_ancestor(focused_widget) if invisible_ancestor: new_focused_widget = next_focus_widget(invisible_ancestor) if new_focused_widget: new_focused_widget.grab_focus() return True def set_cursor(self, new=False, reset_view=True): focus_widget = None if reset_view or not self.widget.has_focus(): if reset_view: for notebook in self.notebooks: notebook.set_current_page(0) if self.attributes.get('cursor') in self.widgets: focus_widget = find_focusable_child(self.widgets[ self.attributes['cursor']][0].widget) else: child = find_focusable_child(self.viewport) if child: child.grab_focus() record = self.record if record: invalid_widgets = [] for name in record.invalid_fields: widgets = self.widgets.get(name, []) for widget in widgets: invalid_widget = find_focusable_child(widget.widget) if invalid_widget: invalid_widgets.append(invalid_widget) if invalid_widgets: focus_widget = find_first_focus_widget( self.viewport, invalid_widgets) if focus_widget: for notebook in self.notebooks: for i in range(notebook.get_n_pages()): child = notebook.get_nth_page(i) if focus_widget.is_ancestor(child): notebook.set_current_page(i) for group in self.expandables: if focus_widget.is_ancestor(group): group.set_expanded(True) focus_widget.grab_focus() def button_clicked(self, widget): widget.handler_block_by_func(self.button_clicked) try: self.screen.button(widget.attrs) finally: widget.handler_unblock_by_func(self.button_clicked) def on_scan_code(self, code): if self.record: modified = self.record.on_scan_code( code, self.attributes.get('scan_code_depends', [])) if self.attributes['scan_code'] == 'submit': window = self.widget.get_toplevel() if window and isinstance(window, Gtk.Window): GLib.idle_add(lambda: window and window.activate_default()) return modified ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1745680285.227445 tryton-7.0.24/tryton/gui/window/view_form/view/form_gtk/0000755000175000017500000000000015003173635021467 5ustar00cedced././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685599.0 tryton-7.0.24/tryton/gui/window/view_form/view/form_gtk/__init__.py0000644000175000017500000000022014517761237023604 0ustar00cedced# This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739470777.0 tryton-7.0.24/tryton/gui/window/view_form/view/form_gtk/binary.py0000644000175000017500000002017214753433671023341 0ustar00cedced# This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. import gettext import os from urllib.parse import unquote, urlparse from gi.repository import Gdk, Gtk from tryton.common import ( Tooltips, common, file_open, file_selection, file_write, url_open) from tryton.common.entry_position import reset_position from .widget import Widget _ = gettext.gettext class BinaryMixin(Widget): def __init__(self, view, attrs): super(BinaryMixin, self).__init__(view, attrs) self.filename = attrs.get('filename') def toolbar(self): 'Return HBox with the toolbar' hbox = Gtk.HBox(spacing=0) tooltips = Tooltips() self.but_save_as = Gtk.Button() self.but_save_as.set_image(common.IconFactory.get_image( 'tryton-download', Gtk.IconSize.SMALL_TOOLBAR)) self.but_save_as.set_relief(Gtk.ReliefStyle.NONE) self.but_save_as.connect('clicked', self.save_as) tooltips.set_tip(self.but_save_as, _('Save As...')) hbox.pack_start(self.but_save_as, expand=False, fill=False, padding=0) self.but_select = Gtk.Button() self.but_select.set_image(common.IconFactory.get_image( 'tryton-search', Gtk.IconSize.SMALL_TOOLBAR)) self.but_select.set_relief(Gtk.ReliefStyle.NONE) self.but_select.connect('clicked', self.select) target_entry = Gtk.TargetEntry.new('text/uri-list', 0, 0) self.but_select.drag_dest_set(Gtk.DestDefaults.ALL, [ target_entry, ], Gdk.DragAction.MOVE | Gdk.DragAction.COPY) self.but_select.connect( 'drag-data-received', self.select_drag_data_received) tooltips.set_tip(self.but_select, _('Select...')) hbox.pack_start(self.but_select, expand=False, fill=False, padding=0) self.but_clear = Gtk.Button() self.but_clear.set_image(common.IconFactory.get_image( 'tryton-clear', Gtk.IconSize.SMALL_TOOLBAR)) self.but_clear.set_relief(Gtk.ReliefStyle.NONE) self.but_clear.connect('clicked', self.clear) tooltips.set_tip(self.but_clear, _('Clear')) hbox.pack_start(self.but_clear, expand=False, fill=False, padding=0) tooltips.enable() return hbox @property def filename_field(self): return self.record.group.fields.get(self.filename) @property def filters(self): filter_all = Gtk.FileFilter() filter_all.set_name(_('All files')) filter_all.add_pattern("*") return [filter_all] @property def preview(self): return False def update_buttons(self, value): if value: self.but_save_as.show() self.but_select.hide() self.but_clear.show() else: self.but_save_as.hide() self.but_select.show() self.but_clear.hide() def select(self, widget=None): if not self.field: return filename = file_selection( _('Select'), preview=self.preview, filters=self.filters) if filename: self._set_uri(filename.as_uri()) def select_drag_data_received( self, widget, context, x, y, selection, info, timestamp): if not self.field: return for uri in selection.get_uris(): self._set_uri(uri) def _set_uri(self, uri): uri = unquote(uri) self.field.set_client(self.record, url_open(uri).read()) if self.filename_field: self.filename_field.set_client(self.record, os.path.basename(urlparse(uri).path)) def get_data(self): if hasattr(self.field, 'get_data'): data = self.field.get_data(self.record) else: data = self.field.get(self.record) if isinstance(data, str): data = data.encode('utf-8') return data def open_(self, widget=None): if not self.filename_field: return filename = self.filename_field.get(self.record) if not filename: return file_path = file_write(filename, self.get_data()) root, type_ = os.path.splitext(filename) if type_: type_ = type_[1:] file_open(file_path, type_) def save_as(self, widget=None): filename = '' if self.filename_field: filename = self.filename_field.get(self.record) filename = file_selection(_('Save As...'), filename=filename, action=Gtk.FileChooserAction.SAVE) if filename: with open(filename, 'wb') as fp: fp.write(self.get_data()) def clear(self, widget=None): if self.filename_field: self.filename_field.set_client(self.record, None) self.field.set_client(self.record, None) class Binary(BinaryMixin, Widget): "Binary" def __init__(self, view, attrs): super(Binary, self).__init__(view, attrs) self.widget = Gtk.HBox(spacing=0) self.wid_size = Gtk.Entry() self.wid_size.set_width_chars(self.default_width_chars) self.wid_size.set_alignment(1.0) self.wid_size.props.sensitive = False if self.filename and attrs.get('filename_visible'): self.wid_text = Gtk.Entry() self.wid_text.set_property('activates_default', True) self.wid_text.connect('focus-out-event', lambda x, y: self._focus_out()) self.wid_text.connect_after('key_press_event', self.sig_key_press) self.wid_text.connect('icon-press', self.sig_icon_press) self.widget.pack_start( self.wid_text, expand=True, fill=True, padding=0) else: self.wid_text = None self.mnemonic_widget = self.wid_text self.widget.pack_start( self.wid_size, expand=not self.filename, fill=True, padding=0) self.widget.pack_start( self.toolbar(), expand=False, fill=False, padding=0) def _readonly_set(self, value): self.but_select.set_sensitive(not value) self.but_clear.set_sensitive(not value) if self.wid_text: self.wid_text.set_editable(not value) def sig_key_press(self, widget, event, *args): editable = self.wid_text and self.wid_text.get_editable() if event.keyval == Gdk.KEY_F3 and editable: self.select(widget) return True elif event.keyval == Gdk.KEY_F2: if self.filename: self.open_() else: self.save_as() return True return False def sig_icon_press(self, widget, icon_pos, event): widget.grab_focus() if icon_pos == Gtk.EntryIconPosition.PRIMARY: self.open_() def display(self): super(Binary, self).display() if not self.field: if self.wid_text: self.wid_text.set_text('') self.wid_size.set_text('') self.but_save_as.hide() return False if hasattr(self.field, 'get_size'): size = self.field.get_size(self.record) else: size = len(self.field.get(self.record)) self.wid_size.set_text(common.humanize(size or 0, 'B')) reset_position(self.wid_size) if self.wid_text: self.wid_text.set_text(self.filename_field.get(self.record) or '') reset_position(self.wid_text) if size: icon, tooltip = 'tryton-open', _("Open...") else: icon, tooltip = None, '' pos = Gtk.EntryIconPosition.PRIMARY if icon: pixbuf = common.IconFactory.get_pixbuf( icon, Gtk.IconSize.MENU) else: pixbuf = None self.wid_text.set_icon_from_pixbuf(pos, pixbuf) self.wid_text.set_icon_tooltip_text(pos, tooltip) self.update_buttons(bool(size)) return True def set_value(self): if self.wid_text: self.filename_field.set_client(self.record, self.wid_text.get_text() or False) return ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685599.0 tryton-7.0.24/tryton/gui/window/view_form/view/form_gtk/calendar_.py0000644000175000017500000001347714517761237023777 0ustar00cedced# This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. import datetime import gettext from gi.repository import GLib, Gtk from tryton import common from tryton.common.datetime_ import Date as DateEntry from tryton.common.datetime_ import DateTime as DateTimeEntry from tryton.common.datetime_ import Time as TimeEntry from tryton.common.datetime_ import add_operators from .widget import Widget _ = gettext.gettext class Date(Widget): _changed_signal = 'date-changed' def __init__(self, view, attrs, _entry=DateEntry): super(Date, self).__init__(view, attrs) self.widget = Gtk.HBox() self.entry = self.mnemonic_widget = add_operators(_entry()) self.real_entry.set_property('activates_default', True) self.real_entry.connect('key_press_event', self.sig_key_press) self.real_entry.connect('activate', self.sig_activate) self.real_entry.connect('changed', lambda _: self.send_modified()) self.real_entry.connect( 'focus-out-event', lambda *a: self._focus_out()) self.entry.connect(self._changed_signal, self.changed) self.widget.pack_start(self.entry, expand=False, fill=False, padding=0) @property def real_entry(self): return self.entry def _set_editable(self, value): self.entry.set_editable(value) self.entry.set_icon_sensitive(Gtk.EntryIconPosition.PRIMARY, value) def _readonly_set(self, value): self._set_editable(not value) @classmethod def cast(cls, value): if isinstance(value, datetime.datetime): value = value.date() return value @property def modified(self): if self.record and self.field: field_value = self.cast(self.field.get_client(self.record)) return field_value != self.get_value() return False def changed(self, widget): def focus_out(): if widget.props.window: self._focus_out() # Must be deferred because it triggers a display of the form GLib.idle_add(focus_out) def sig_key_press(self, widget, event): self.send_modified() def set_value(self): self.field.set_client(self.record, self.get_value()) def get_value(self): self.entry.parse() return self.entry.props.value def set_format(self): if self.field and self.record: format_ = self.field.date_format(self.record) else: format_ = common.date_format( self.view.screen.context.get('date_format')) self.entry.props.format = format_ def display(self): super(Date, self).display() if self.field and self.record: value = self.field.get_client(self.record) else: value = '' self.entry.props.value = value self.set_format() def _move_active(combobox, scroll_type): if not combobox.get_child().get_editable(): combobox.stop_emission_by_name('move-active') class Time(Date): _changed_signal = 'time-changed' def __init__(self, view, attrs): super(Time, self).__init__(view, attrs, _entry=TimeEntry) self.entry.connect('move-active', _move_active) self.entry.connect( 'scroll-event', lambda c, e: c.stop_emission_by_name('scroll-event')) def _set_editable(self, value): self.entry.set_sensitive(value) @classmethod def cast(cls, value): if isinstance(value, datetime.datetime): value = value.time() return value @property def real_entry(self): return self.entry.get_child() def display(self): super(Time, self).display() def set_format(self): if self.field and self.record: format_ = self.field.time_format(self.record) else: format_ = '%X' self.entry.props.format = format_ class DateTime(Date): _changed_signal = 'datetime-changed' def __init__(self, view, attrs): Widget.__init__(self, view, attrs) self.widget = Gtk.HBox() self.entry = self.mnemonic_widget = DateTimeEntry() for child in self.entry.get_children(): add_operators(child) if isinstance(child, Gtk.ComboBox): child.connect('move-active', _move_active) child.connect( 'scroll-event', lambda c, e: c.stop_emission_by_name('scroll-event')) child = child.get_child() child.set_property('activates_default', True) child.connect('key_press_event', self.sig_key_press) child.connect('activate', self.sig_activate) child.connect('changed', lambda _: self.send_modified()) child.connect('focus-out-event', lambda *a: self._focus_out()) self.entry.connect(self._changed_signal, self.changed) self.widget.pack_start(self.entry, expand=False, fill=False, padding=0) @classmethod def cast(cls, value): return value def _set_editable(self, value): for child in self.entry.get_children(): if isinstance(child, Gtk.Entry): child.set_editable(value) child.set_icon_sensitive(Gtk.EntryIconPosition.PRIMARY, value) elif isinstance(child, Gtk.ComboBox): child.set_sensitive(value) def set_format(self): if self.field and self.record: date_format = self.field.date_format(self.record) time_format = self.field.time_format(self.record) else: date_format = common.date_format( self.view.screen.context.get('date_format')) time_format = '%X' self.entry.props.date_format = date_format self.entry.props.time_format = time_format ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1725720314.0 tryton-7.0.24/tryton/gui/window/view_form/view/form_gtk/char.py0000644000175000017500000001457514667063372023005 0ustar00cedced# This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. import gettext from gi.repository import GLib, Gtk from tryton.common import IconFactory, Tooltips from tryton.common.entry_position import reset_position from tryton.common.selection import PopdownMixin, selection_shortcuts from .widget import TranslateMixin, Widget _ = gettext.gettext class Char(Widget, TranslateMixin, PopdownMixin): "Char" def __init__(self, view, attrs): super(Char, self).__init__(view, attrs) self.widget = Gtk.HBox() self.autocomplete = bool(attrs.get('autocomplete')) if self.autocomplete: self.entry = Gtk.ComboBox(has_entry=True) selection_shortcuts(self.entry) focus_entry = self.entry.get_child() self.set_popdown([], self.entry) self.entry.connect('changed', self.changed) self.entry.connect('move-active', self._move_active) self.entry.connect( 'scroll-event', lambda c, e: c.stop_emission_by_name('scroll-event')) else: self.entry = Gtk.Entry() focus_entry = self.entry self.mnemonic_widget = focus_entry focus_entry.set_property('activates_default', True) focus_entry.connect('activate', self.sig_activate) focus_entry.connect('focus-out-event', lambda x, y: self._focus_out()) focus_entry.connect('key-press-event', self.send_modified) expand, fill = True, True if attrs.get('size'): expand, fill = False, False self.widget.pack_start(self.entry, expand=expand, fill=fill, padding=0) if attrs.get('translate'): self.entry.set_icon_from_pixbuf( Gtk.EntryIconPosition.SECONDARY, IconFactory.get_pixbuf('tryton-translate', Gtk.IconSize.MENU)) self.entry.connect('icon-press', self.translate) def translate_widget(self): entry = Gtk.Entry() entry.set_property('activates_default', True) if self.record: field_size = self.record.expr_eval(self.attrs.get('size')) entry.set_width_chars( min(120, field_size or self.default_width_chars)) entry.set_max_length(field_size or 0) return entry def translate_widget_set(self, widget, value): widget.set_text(value) reset_position(widget) def translate_widget_get(self, widget): return widget.get_text() def translate_widget_set_readonly(self, widget, value): widget.set_editable(not value) widget.props.sensitive = not value def changed(self, combobox): def focus_out(): if combobox.props.window: self._focus_out() # Only when changed from pop list if not combobox.get_child().has_focus(): # Must be deferred because it triggers a display of the form GLib.idle_add(focus_out) @property def modified(self): if self.record and self.field: value = self.get_client_value() return value != self.get_value() return False def set_value(self): entry = self.entry.get_child() if self.autocomplete else self.entry value = entry.get_text() or '' return self.field.set_client(self.record, value) def get_value(self): entry = self.entry.get_child() if self.autocomplete else self.entry return entry.get_text() def get_client_value(self): if not self.field: value = '' else: value = self.field.get_client(self.record) return value def display(self): super(Char, self).display() if self.autocomplete: if self.record: if self.field_name not in self.record.autocompletion: self.record.do_autocomplete(self.field_name) selection = self.record.autocompletion.get(self.field_name, []) else: selection = [] self.set_popdown([(x, x) for x in selection], self.entry) # Set size if self.autocomplete: size_entry = self.entry.get_child() else: size_entry = self.entry if self.record: field_size = self.record.expr_eval(self.attrs.get('size')) size_entry.set_width_chars( min(120, field_size or self.default_width_chars)) size_entry.set_max_length(field_size or 0) else: size_entry.set_width_chars(self.default_width_chars) size_entry.set_max_length(0) value = self.get_client_value() if not self.autocomplete: self.entry.set_text(value) reset_position(self.entry) else: self.entry.handler_block_by_func(self.changed) if not self.set_popdown_value(self.entry, value) or not value: child = self.entry.get_child() child.set_text(value) reset_position(child) self.entry.handler_unblock_by_func(self.changed) def _move_active(self, combobox, scroll_type): if not combobox.get_child().get_editable(): combobox.stop_emission_by_name('move-active') def _readonly_set(self, value): sensitivity = { True: Gtk.SensitivityType.OFF, False: Gtk.SensitivityType.AUTO, } super(Char, self)._readonly_set(value) if self.autocomplete: entry_editable = self.entry.get_child() self.entry.set_button_sensitivity(sensitivity[value]) else: entry_editable = self.entry entry_editable.set_editable(not value) class Password(Char): def __init__(self, view, attrs): super(Password, self).__init__(view, attrs) self.entry.props.visibility = False self.visibility_checkbox = Gtk.CheckButton() self.visibility_checkbox.connect('toggled', self.toggle_visibility) Tooltips().set_tip(self.visibility_checkbox, _('Show plain text')) self.widget.pack_start( self.visibility_checkbox, expand=False, fill=True, padding=0) def toggle_visibility(self, button): if self.autocomplete: entry = self.entry.get_child() else: entry = self.entry entry.props.visibility = not self.entry.props.visibility ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685599.0 tryton-7.0.24/tryton/gui/window/view_form/view/form_gtk/checkbox.py0000644000175000017500000000231514517761237023642 0ustar00cedced# This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. import gettext from gi.repository import Gtk from .widget import Widget _ = gettext.gettext class CheckBox(Widget): def __init__(self, view, attrs): super(CheckBox, self).__init__(view, attrs) self.widget = self.mnemonic_widget = Gtk.CheckButton() self.widget.connect('focus-out-event', lambda x, y: self._focus_out()) self.widget.connect_after('toggled', self.sig_activate) def _readonly_set(self, value): super(CheckBox, self)._readonly_set(value) # TODO find a better solution to accept focus self.widget.set_sensitive(not value) def set_value(self): self.field.set_client(self.record, self.widget.get_active()) def display(self): super(CheckBox, self).display() if not self.field: self.widget.set_active(False) return False self.widget.handler_block_by_func(self.sig_activate) try: self.widget.set_active(bool(self.field.get(self.record))) finally: self.widget.handler_unblock_by_func(self.sig_activate) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1730238401.0 tryton-7.0.24/tryton/gui/window/view_form/view/form_gtk/dictionary.py0000644000175000017500000005340714710253701024214 0ustar00cedced# This file is part of Tryton. The COPYRIGHT file at the top level of this # repository contains the full copyright notices and license terms. import datetime as dt import decimal import gettext import locale import operator from collections import defaultdict from decimal import Decimal from gi.repository import GLib, GObject, Gtk from tryton.common import ( IconFactory, Tooltips, timezoned_date, untimezoned_date) from tryton.common.completion import get_completion, update_completion from tryton.common.datetime_ import Date, DateTime, add_operators from tryton.common.domain_inversion import eval_domain from tryton.common.domain_parser import quote from tryton.common.entry_position import reset_position from tryton.common.number_entry import NumberEntry from tryton.common.selection import selection_shortcuts from tryton.common.underline import set_underline from tryton.common.widget_style import widget_class from tryton.gui.window.win_search import WinSearch from tryton.pyson import PYSONDecoder from .widget import Widget _ = gettext.gettext class DictEntry(object): expand = True fill = True def __init__(self, name, parent_widget): self.name = name self.definition = parent_widget.field.keys[name] self.parent_widget = parent_widget self._signal_handlers = defaultdict(list) self.widget = self.create_widget() if self.definition.get('help'): parent_widget.tooltips.set_tip( self.widget, self.definition['help']) def create_widget(self): widget = Gtk.Entry() self._signal_handlers[widget].append( widget.connect('key-press-event', self.parent_widget.send_modified)) self._signal_handlers[widget].append( widget.connect('focus-out-event', lambda w, e: self.parent_widget._focus_out())) widget.props.activates_default = True self._signal_handlers[widget].append( widget.connect('activate', self.parent_widget.sig_activate)) return widget def modified(self, value): return self.get_value() != value.get(self.name) def get_value(self): return self.widget.get_text() def set_value(self, value): self.widget.set_text(str(value or '')) reset_position(self.widget) def set_readonly(self, readonly): self.widget.set_editable(not readonly) def disconnect_signals(self): for widget, signal_ids in self._signal_handlers.items(): for handler_id in signal_ids: widget.disconnect(handler_id) class DictCharEntry(DictEntry): def modified(self, value): return self.get_value() != (value.get(self.name, '') or '') class DictBooleanEntry(DictEntry): def create_widget(self): widget = Gtk.CheckButton() self._signal_handlers[widget].append( widget.connect('toggled', self.parent_widget.sig_activate)) self._signal_handlers[widget].append( widget.connect('focus-out-event', lambda w, e: self.parent_widget._focus_out())) return widget def get_value(self): return self.widget.props.active def set_value(self, value): self.widget.handler_block_by_func(self.parent_widget.sig_activate) try: self.widget.props.active = bool(value) finally: self.widget.handler_unblock_by_func( self.parent_widget.sig_activate) def set_readonly(self, readonly): self.widget.set_sensitive(not readonly) class DictSelectionEntry(DictEntry): expand = False fill = False def create_widget(self): widget = Gtk.ComboBox(has_entry=True) # customizing entry child = widget.get_child() child.props.activates_default = True self._signal_handlers[child].append( child.connect('changed', self._changed)) self._signal_handlers[child].append( child.connect('focus-out-event', lambda w, e: self.parent_widget._focus_out())) self._signal_handlers[child].append( child.connect('activate', lambda w: self.parent_widget._focus_out())) widget.connect( 'scroll-event', lambda c, e: c.stop_emission_by_name('scroll-event')) selection_shortcuts(widget) # setting completion and selection model = Gtk.ListStore(GObject.TYPE_STRING, GObject.TYPE_PYOBJECT) model.append(('', "")) width = 10 selection = self.definition['selection'] if self.definition.get('sort', True): selection.sort(key=operator.itemgetter(1)) for value, name in selection: name = str(name) model.append((name, value)) width = max(width, len(name)) widget.set_model(model) widget.set_entry_text_column(0) child.set_width_chars(width) completion = Gtk.EntryCompletion() completion.set_inline_selection(True) completion.set_model(model) child.set_completion(completion) completion.set_text_column(0) completion.connect('match-selected', self.match_selected) return widget def match_selected(self, completion, model, iter_): value, = model.get(iter_, 1) model = self.widget.get_model() for i, selection in enumerate(model): if selection[1] == value: GLib.idle_add(self.widget.set_active, i) break def get_value(self): active = self.widget.get_active() if active < 0: return None else: model = self.widget.get_model() return model[active][1] def set_value(self, value): active = -1 model = self.widget.get_model() for i, selection in enumerate(model): if selection[1] == value: active = i break child = self.widget.get_child() child.handler_block_by_func(self._changed) try: self.widget.set_active(active) if active == -1: # When setting no item GTK doesn't clear the entry child.set_text('') finally: child.handler_unblock_by_func(self._changed) def _changed(self, selection): GLib.idle_add(self.parent_widget._focus_out) def set_readonly(self, readonly): self.widget.set_sensitive(not readonly) class DictMultiSelectionEntry(DictEntry): expand = False def create_widget(self): widget = Gtk.ScrolledWindow() widget.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC) widget.set_shadow_type(Gtk.ShadowType.ETCHED_IN) widget.set_size_request(100, 100) model = Gtk.ListStore(GObject.TYPE_STRING, GObject.TYPE_STRING) self.tree = Gtk.TreeView() self.tree.set_model(model) self.tree.set_search_column(1) self._signal_handlers[self.tree].append( self.tree.connect('focus-out-event', lambda w, e: self.parent_widget._focus_out())) self.tree.set_headers_visible(False) selection = self.tree.get_selection() selection.set_mode(Gtk.SelectionMode.MULTIPLE) self._signal_handlers[selection].append( selection.connect('changed', self._changed)) widget.add(self.tree) self.selection = self.definition['selection'] if self.definition.get('sort', True): self.selection.sort(key=operator.itemgetter(1)) for value, name in self.selection: name = str(name) model.append((value, name)) name_column = Gtk.TreeViewColumn() name_cell = Gtk.CellRendererText() name_column.pack_start(name_cell, expand=True) name_column.add_attribute(name_cell, 'text', 1) self.tree.append_column(name_column) return widget def get_value(self): model, paths = self.tree.get_selection().get_selected_rows() return [model[path][0] for path in paths] def set_value(self, value): value2path = {v: idx for idx, (v, _) in enumerate(self.selection)} selection = self.tree.get_selection() selection.handler_block_by_func(self._changed) try: selection.unselect_all() if value: for v in value: if v in value2path: selection.select_path(value2path[v]) finally: selection.handler_unblock_by_func(self._changed) def _changed(self, selection): GLib.idle_add(self.parent_widget._focus_out) def set_readonly(self, readonly): selection = self.tree.get_selection() selection.set_select_function(lambda *a: not readonly) class DictIntegerEntry(DictEntry): expand = False fill = False def create_widget(self): widget = NumberEntry() self._signal_handlers[widget].append( widget.connect('key-press-event', self.parent_widget.send_modified)) self._signal_handlers[widget].append( widget.connect('focus-out-event', lambda w, e: self.parent_widget._focus_out())) widget.props.activates_default = True self._signal_handlers[widget].append( widget.connect('activate', self.parent_widget.sig_activate)) return widget def get_value(self): if self.widget.value is not None: return int(self.widget.value) return None def set_value(self, value): if isinstance(value, (int, float, Decimal)): txt_val = locale.format_string('%d', value, True) else: txt_val = '' self.widget.set_text(txt_val) reset_position(self.widget) class DictFloatEntry(DictIntegerEntry): @property def digits(self): record = self.parent_widget.record if record: digits = record.expr_eval(self.definition.get('digits')) if not digits or any(d is None for d in digits): return return digits @property def width(self): digits = self.digits if digits: return sum(digits) else: return 18 def get_value(self): return self.widget.value def set_value(self, value): digits = self.digits if digits: self.widget.digits = digits[1] else: self.widget.digits = None self.widget.set_width_chars(self.width) if isinstance(value, (int, float, Decimal)): txt_val = locale.localize( '{0:.{1}f}'.format(value, digits[1]), True) else: txt_val = '' self.widget.set_text(txt_val) reset_position(self.widget) class DictNumericEntry(DictFloatEntry): def get_value(self): txt_value = self.widget.get_text() if txt_value: try: return Decimal(locale.delocalize(txt_value)) except decimal.InvalidOperation: pass return None class DictDateTimeEntry(DictEntry): expand = False fill = False def create_widget(self): widget = add_operators(DateTime()) record = self.parent_widget.record field = self.parent_widget.field if record and field: date_format = field.date_format(record) time_format = field.time_format(record) widget.props.date_format = date_format widget.props.time_format = time_format self._signal_handlers[widget].append( widget.connect('key_press_event', self.parent_widget.send_modified)) self._signal_handlers[widget].append( widget.connect('focus-out-event', lambda w, e: self.parent_widget._focus_out())) return widget def get_value(self): return untimezoned_date(self.widget.props.value) def set_value(self, value): self.widget.props.value = ( timezoned_date(value) if isinstance(value, dt.datetime) else None) def set_readonly(self, readonly): for child in self.widget.get_children(): if isinstance(child, Gtk.Entry): child.set_editable(not readonly) child.set_icon_sensitive( Gtk.EntryIconPosition.PRIMARY, not readonly) elif isinstance(child, Gtk.ComboBox): child.set_sensitive(not readonly) class DictDateEntry(DictEntry): expand = False fill = False def create_widget(self): widget = add_operators(Date()) record = self.parent_widget.record field = self.parent_widget.field if record and field: format_ = field.date_format(record) widget.props.format = format_ self._signal_handlers[widget].append( widget.connect('key_press_event', self.parent_widget.send_modified)) self._signal_handlers[widget].append( widget.connect('focus-out-event', lambda w, e: self.parent_widget._focus_out())) return widget def get_value(self): return self.widget.props.value def set_value(self, value): self.widget.props.value = value if isinstance(value, dt.date) else None def set_readonly(self, readonly): super().set_readonly(readonly) self.widget.set_icon_sensitive( Gtk.EntryIconPosition.PRIMARY, not readonly) DICT_ENTRIES = { 'char': DictCharEntry, 'boolean': DictBooleanEntry, 'selection': DictSelectionEntry, 'multiselection': DictMultiSelectionEntry, 'datetime': DictDateTimeEntry, 'date': DictDateEntry, 'integer': DictIntegerEntry, 'float': DictFloatEntry, 'numeric': DictNumericEntry, } class DictWidget(Widget): def __init__(self, view, attrs): super(DictWidget, self).__init__(view, attrs) self.schema_model = attrs['schema_model'] self.fields = {} self.buttons = {} self.rows = {} self.widget = Gtk.Frame() label = Gtk.Label(label=set_underline(attrs.get('string', ''))) label.set_use_underline(True) self.widget.set_label_widget(label) self.widget.set_shadow_type(Gtk.ShadowType.OUT) vbox = Gtk.VBox() self.widget.add(vbox) self.grid = Gtk.Grid(column_spacing=3, row_spacing=3) vbox.pack_start(self.grid, expand=True, fill=True, padding=0) hbox = Gtk.HBox() hbox.set_border_width(2) self.wid_text = Gtk.Entry() self.wid_text.set_placeholder_text(_('Search')) self.wid_text.props.width_chars = 13 self.wid_text.connect('activate', self._sig_activate) hbox.pack_start(self.wid_text, expand=True, fill=True, padding=0) label.set_mnemonic_widget(self.wid_text) if int(self.attrs.get('completion', 1)): self.wid_completion = get_completion(search=False, create=False) self.wid_completion.connect('match-selected', self._completion_match_selected) self.wid_text.set_completion(self.wid_completion) self.wid_text.connect('changed', self._update_completion) else: self.wid_completion = None self.but_add = Gtk.Button(can_focus=False) self.but_add.connect('clicked', self._sig_add) self.but_add.add( IconFactory.get_image('tryton-add', Gtk.IconSize.SMALL_TOOLBAR)) self.but_add.set_relief(Gtk.ReliefStyle.NONE) hbox.pack_start(self.but_add, expand=False, fill=False, padding=0) vbox.pack_start(hbox, expand=True, fill=True, padding=0) self.tooltips = Tooltips() self.tooltips.set_tip(self.but_add, _('Add value')) self.tooltips.enable() self._readonly = False self._record_id = None @property def _invalid_widget(self): return self.wid_text def _new_remove_btn(self): but_remove = Gtk.Button() but_remove.add( IconFactory.get_image('tryton-remove', Gtk.IconSize.SMALL_TOOLBAR)) but_remove.set_relief(Gtk.ReliefStyle.NONE) return but_remove def _sig_activate(self, *args): if self.wid_text.get_editable(): self._sig_add() def _sig_add(self, *args): context = self.field.get_context(self.record) value = self.wid_text.get_text() domain = self.field.domain_get(self.record) def callback(result): if result: self.add_new_keys([r[0] for r in result]) self.wid_text.set_text('') win = WinSearch(self.schema_model, callback, sel_multi=True, context=context, domain=domain, new=False) win.screen.search_filter(quote(value)) win.show() def add_new_keys(self, ids): new_keys = self.field.add_new_keys(ids, self.record) self.send_modified() focus = False for key_name in new_keys: if key_name not in self.fields: self.add_line(key_name) if not focus: # Use idle add because it can be called from the callback # of WinSearch while the popup is still there GLib.idle_add(self.fields[key_name].widget.grab_focus) focus = True def _sig_remove(self, button, key, modified=True): self.fields[key].disconnect_signals() del self.fields[key] del self.buttons[key] for widget in self.rows[key]: self.grid.remove(widget) widget.destroy() del self.rows[key] if modified: self.send_modified() self.set_value() def set_value(self): self.field.set_client(self.record, self.get_value()) def get_value(self): return dict((key, widget.get_value()) for key, widget in list(self.fields.items())) @property def modified(self): if self.record and self.field: value = self.field.get_client(self.record) return any(widget.modified(value) for widget in self.fields.values()) return False def _readonly_set(self, readonly): self._readonly = readonly self._set_button_sensitive() for widget in list(self.fields.values()): widget.set_readonly(readonly) self.wid_text.set_sensitive(not readonly) self.wid_text.set_editable(not readonly) def _set_button_sensitive(self): self.but_add.set_sensitive(bool( not self._readonly and int(self.attrs.get('create', 1)))) for button in self.buttons.values(): button.set_sensitive(bool( not self._readonly and self.attrs.get('delete', True))) def add_line(self, key): key_schema = self.field.keys[key] self.fields[key] = DICT_ENTRIES[key_schema['type']](key, self) field = self.fields[key] text = key_schema['string'] + _(':') label = Gtk.Label( label=set_underline(text), use_underline=True, halign=Gtk.Align.END) self.grid.attach_next_to( label, None, Gtk.PositionType.BOTTOM, 1, 1) label.set_mnemonic_widget(field.widget) label.show() hbox = Gtk.HBox(hexpand=True) hbox.pack_start( field.widget, expand=field.expand, fill=field.fill, padding=0) self.grid.attach_next_to( hbox, label, Gtk.PositionType.RIGHT, 1, 1) hbox.show_all() remove_but = self._new_remove_btn() self.tooltips.set_tip(remove_but, _('Remove "%s"') % key_schema['string']) self.grid.attach_next_to( remove_but, hbox, Gtk.PositionType.RIGHT, 1, 1) remove_but.connect('clicked', self._sig_remove, key) remove_but.show_all() self.rows[key] = [label, hbox, remove_but] self.buttons[key] = remove_but def display(self): super(DictWidget, self).display() if not self.field: return record_id = self.record.id if self.record else None if record_id != self._record_id: for key in list(self.fields.keys()): self._sig_remove(None, key, modified=False) self._record_id = record_id value = self.field.get_client(self.record) if self.field else {} new_key_names = set(value.keys()) - set(self.field.keys) if new_key_names: self.field.add_keys(list(new_key_names), self.record) decoder = PYSONDecoder() def filter_func(item): key, value = item return key in self.field.keys def key(item): key, value = item return self.field.keys[key]['sequence'] or 0 for key, val in sorted(filter(filter_func, value.items()), key=key): if key not in self.fields: self.add_line(key) widget = self.fields[key] widget.set_value(val) widget.set_readonly(self._readonly) key_domain = decoder.decode( self.field.keys[key].get('domain') or '[]') widget_class( widget.widget, 'invalid', not eval_domain(key_domain, value)) for key in set(self.fields.keys()) - set(value.keys()): self._sig_remove(None, key, modified=False) self._set_button_sensitive() def _completion_match_selected(self, completion, model, iter_): record_id, = model.get(iter_, 1) self.add_new_keys([record_id]) self.wid_text.set_text('') completion_model = self.wid_completion.get_model() completion_model.clear() completion_model.search_text = self.wid_text.get_text() return True def _update_completion(self, widget): if not self.wid_text.get_editable(): return if not self.record: return update_completion(self.wid_text, self.record, self.field, self.schema_model) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1725720314.0 tryton-7.0.24/tryton/gui/window/view_form/view/form_gtk/document.py0000644000175000017500000000643414667063372023701 0ustar00cedced# This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. from pathlib import Path from gi.repository import Gdk, GLib, Gtk try: from gi.repository import EvinceDocument, EvinceView EvinceDocument.init() except ImportError: EvinceDocument = EvinceView = None from tryton.common import data2pixbuf, resize_pixbuf from .binary import BinaryMixin from .widget import Widget class Document(BinaryMixin, Widget): expand = True def __init__(self, view, attrs): super().__init__(view, attrs) self.widget = Gtk.VBox(spacing=3) self.image = Gtk.DrawingArea() self.widget.pack_start( self.image, expand=True, fill=True, padding=0) if EvinceView: self.evince_view = EvinceView.View() self.evince_scroll = Gtk.ScrolledWindow() self.evince_scroll.add(self.evince_view) self.widget.pack_start( self.evince_scroll, expand=True, fill=True, padding=0) else: self.evince_view = self.evince_scroll = None def _draw(self, drawing_area, cr, pixbuf): width = drawing_area.get_allocated_width() height = drawing_area.get_allocated_height() pixbuf = resize_pixbuf(pixbuf, width, height) width = (width - pixbuf.get_width()) / 2 height = (height - pixbuf.get_height()) / 2 Gdk.cairo_set_source_pixbuf(cr, pixbuf, width, height) cr.paint() def display(self): super().display() if self.field and self.record: data = self.field.get_data(self.record) else: data = None if not data: self.image.hide() if self.evince_view: self.evince_view.set_model(EvinceView.DocumentModel()) self.evince_scroll.hide() return pixbuf = data2pixbuf(data) if pixbuf: try: self.image.disconnect_by_func(self._draw) except TypeError: pass self.image.connect('draw', self._draw, pixbuf) self.image.queue_draw() self.image.show() if self.evince_view: self.evince_view.set_model(EvinceView.DocumentModel()) self.evince_scroll.hide() else: self.image.hide() if self.evince_view: self.evince_scroll.show() suffix = None if self.filename_field: filename = self.filename_field.get(self.record) if filename: suffix = Path(filename).suffix filename = Path(self.field.get_filename(self.record, suffix)) try: document = ( EvinceDocument.Document.factory_get_document_full( filename.as_uri(), EvinceDocument.DocumentLoadFlags.NONE)) model = EvinceView.DocumentModel() model.set_document(document) self.evince_view.set_model(model) except GLib.GError: self.evince_view.set_model(EvinceView.DocumentModel()) self.evince_scroll.hide() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685599.0 tryton-7.0.24/tryton/gui/window/view_form/view/form_gtk/float.py0000644000175000017500000000133514517761237023162 0ustar00cedced# This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. from .integer import Integer class Float(Integer): "Float" @property def digits(self): if self.field and self.record: return self.field.digits(self.record, factor=self.factor) @property def width(self): digits = self.digits if digits: return sum(digits) else: return self.attrs.get('width', 18) def display(self): digits = self.digits if digits: self.entry.digits = digits[1] else: self.entry.digits = None super(Float, self).display() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685599.0 tryton-7.0.24/tryton/gui/window/view_form/view/form_gtk/image.py0000644000175000017500000000762014517761237023142 0ustar00cedced# This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. import gettext from gi.repository import Gdk, Gtk from tryton.common import data2pixbuf, resize_pixbuf, url_open from tryton.config import CONFIG from .binary import BinaryMixin from .widget import Widget _ = gettext.gettext class Image(BinaryMixin, Widget): def __init__(self, view, attrs): super(Image, self).__init__(view, attrs) self.height = int(attrs.get('height', 100)) self.width = int(attrs.get('width', 300)) self.widget = Gtk.Frame() self.widget.set_shadow_type(Gtk.ShadowType.ETCHED_IN) self.widget.get_accessible().set_name(attrs.get('string', '')) vbox = Gtk.VBox(spacing=3, margin=5) self.widget.add(vbox) self.event = Gtk.EventBox() self.event.drag_dest_set(Gtk.DestDefaults.ALL, [ Gtk.TargetEntry.new('text/plain', 0, 0), Gtk.TargetEntry.new('text/uri-list', 0, 1), Gtk.TargetEntry.new("image/x-xpixmap", 0, 2)], Gdk.DragAction.MOVE) self.event.connect('drag_motion', self.drag_motion) self.event.connect('drag_data_received', self.drag_data_received) self.image = Gtk.Image() self.event.add(self.image) vbox.pack_start(self.event, expand=True, fill=True, padding=0) toolbar = self.toolbar() # Set button attributes even if not display if not attrs.get('readonly'): toolbar.set_halign(Gtk.Align.CENTER) vbox.pack_start( toolbar, expand=False, fill=False, padding=0) self._readonly = False @property def filters(self): filters = super(Image, self).filters filter_image = Gtk.FileFilter() filter_image.set_name(_('Images')) for mime in ("image/png", "image/jpeg", "image/gif"): filter_image.add_mime_type(mime) for pat in ("*.png", "*.jpg", "*.gif", "*.tif", "*.xpm"): filter_image.add_pattern(pat) filters.insert(0, filter_image) return filters def _readonly_set(self, value): self._readonly = value self.but_select.set_sensitive(not value) self.but_clear.set_sensitive(not value) def clear(self, widget=None): super(Image, self).clear(widget=widget) self.update_img() def drag_motion(self, widget, context, x, y, timestamp): if self._readonly: return False Gdk.drag_status(context, Gdk.DragAction.COPY, timestamp) return True def drag_data_received(self, widget, context, x, y, selection, info, timestamp): if self._readonly: return if info == 0: uri = selection.get_text().split('\n')[0] if uri: self.field.set_client(self.record, url_open(uri).read()) self.update_img() elif info == 1: uri = selection.get_data().split('\r\n')[0] if uri: self.field.set_client(self.record, url_open(uri).read()) self.update_img() elif info == 2: data = selection.get_pixbuf() if data: self.field.set_client(self.record, data) self.update_img() def update_img(self): value = None if self.field: value = self.field.get_client(self.record) if isinstance(value, int): if value > CONFIG['image.max_size']: value = False else: value = self.field.get_data(self.record) pixbuf = data2pixbuf(value) if pixbuf: pixbuf = resize_pixbuf(pixbuf, self.width, self.height) self.image.set_from_pixbuf(pixbuf) return bool(value) def display(self): super(Image, self).display() value = self.update_img() self.update_buttons(bool(value)) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685599.0 tryton-7.0.24/tryton/gui/window/view_form/view/form_gtk/integer.py0000644000175000017500000000602014517761237023506 0ustar00cedced# This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. from gi.repository import Gtk from tryton.common.entry_position import reset_position from tryton.common.number_entry import NumberEntry from .widget import Widget class Integer(Widget): "Integer" def __init__(self, view, attrs): super(Integer, self).__init__(view, attrs) self.widget = Gtk.HBox() self.entry = self.mnemonic_widget = NumberEntry( monetary=attrs.get('monetary', False)) self.entry.props.activates_default = True self.entry.connect('activate', self.sig_activate) self.entry.connect('focus-out-event', lambda *a: self._focus_out()) self.entry.connect('key-press-event', self.send_modified) self.symbol = attrs.get('symbol') if self.symbol: self.symbol_start = Gtk.Entry(editable=False) self.widget.pack_start( self.symbol_start, expand=False, fill=False, padding=1) self.widget.pack_start(self.entry, expand=False, fill=False, padding=0) self.factor = float(attrs.get('factor', 1)) self.grouping = bool(int(attrs.get('grouping', 1))) if self.symbol: self.symbol_end = Gtk.Entry(editable=False) self.widget.pack_start( self.symbol_end, expand=False, fill=False, padding=1) @property def modified(self): if self.record and self.field: value = self.get_client_value() return value != self.get_value() return False def set_value(self): return self.field.set_client( self.record, self.entry.get_text(), factor=self.factor) def get_value(self): return self.entry.get_text() def get_client_value(self): if not self.field: value = '' else: value = self.field.get_client( self.record, factor=self.factor, grouping=self.grouping) return value @property def width(self): return self.attrs.get('width', 8) def display(self): def set_symbol(entry, text): if text: entry.set_text(text) entry.set_width_chars(len(text) + 1) entry.show() else: entry.set_text('') entry.hide() super().display() self.entry.set_width_chars(self.width) if self.field and self.symbol: symbol, position = self.field.get_symbol(self.record, self.symbol) if position < 0.5: set_symbol(self.symbol_start, symbol) set_symbol(self.symbol_end, '') else: set_symbol(self.symbol_start, '') set_symbol(self.symbol_end, symbol) value = self.get_client_value() self.entry.set_text(value) reset_position(self.entry) def _readonly_set(self, value): super()._readonly_set(value) self.entry.set_editable(not value) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1707150267.0 tryton-7.0.24/tryton/gui/window/view_form/view/form_gtk/many2many.py0000644000175000017500000003101014560205673023754 0ustar00cedced# This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. import gettext from gi.repository import Gdk, Gtk import tryton.common as common from tryton.common.completion import get_completion, update_completion from tryton.common.domain_parser import quote from tryton.common.underline import set_underline from tryton.gui.window.view_form.screen import Screen from tryton.gui.window.win_form import WinForm from tryton.gui.window.win_search import WinSearch from .widget import Widget _ = gettext.gettext class Many2Many(Widget): expand = True def __init__(self, view, attrs): super(Many2Many, self).__init__(view, attrs) self.widget = Gtk.Frame() self.widget.set_shadow_type(Gtk.ShadowType.NONE) self.widget.get_accessible().set_name(attrs.get('string', '')) vbox = Gtk.VBox(homogeneous=False, spacing=5) self.widget.add(vbox) self._readonly = True self._required = False self._position = 0 hbox = Gtk.HBox(homogeneous=False, spacing=0) hbox.set_border_width(2) self.title = Gtk.Label( label=set_underline(attrs.get('string', '')), use_underline=True, halign=Gtk.Align.START) hbox.pack_start(self.title, expand=True, fill=True, padding=0) hbox.pack_start(Gtk.VSeparator(), expand=False, fill=True, padding=0) tooltips = common.Tooltips() self.wid_text = Gtk.Entry() self.wid_text.set_placeholder_text(_('Search')) self.wid_text.set_property('width_chars', 13) self.wid_text.connect('focus-out-event', self._focus_out) self.focus_out = True hbox.pack_start(self.wid_text, expand=True, fill=True, padding=0) if int(self.attrs.get('completion', 1)): self.wid_completion = get_completion( search=self.read_access, create=self.create_access) self.wid_completion.connect('match-selected', self._completion_match_selected) self.wid_completion.connect('action-activated', self._completion_action_activated) self.wid_text.set_completion(self.wid_completion) self.wid_text.connect('changed', self._update_completion) else: self.wid_completion = None self.but_add = Gtk.Button(can_focus=False) tooltips.set_tip(self.but_add, _('Add existing record')) self.but_add.connect('clicked', self._sig_add) self.but_add.add(common.IconFactory.get_image( 'tryton-add', Gtk.IconSize.SMALL_TOOLBAR)) self.but_add.set_relief(Gtk.ReliefStyle.NONE) hbox.pack_start(self.but_add, expand=False, fill=False, padding=0) self.label = Gtk.Label(label='(_/0)') hbox.pack_start(self.label, expand=False, fill=False, padding=0) self.but_remove = Gtk.Button(can_focus=False) tooltips.set_tip(self.but_remove, _('Remove selected record')) self.but_remove.connect('clicked', self._sig_remove) self.but_remove.add(common.IconFactory.get_image( 'tryton-remove', Gtk.IconSize.SMALL_TOOLBAR)) self.but_remove.set_relief(Gtk.ReliefStyle.NONE) hbox.pack_start(self.but_remove, expand=False, fill=False, padding=0) tooltips.enable() frame = Gtk.Frame() frame.add(hbox) frame.set_shadow_type(Gtk.ShadowType.OUT) vbox.pack_start(frame, expand=False, fill=True, padding=0) model = attrs['relation'] breadcrumb = list(self.view.screen.breadcrumb) breadcrumb.append( attrs.get('string') or common.MODELNAME.get(model)) self.screen = Screen(model, view_ids=attrs.get('view_ids', '').split(','), mode=['tree'], views_preload=attrs.get('views', {}), order=attrs.get('order'), row_activate=self._on_activate, readonly=True, limit=None, context=self.view.screen.context, breadcrumb=breadcrumb) self.screen.windows.append(self) vbox.pack_start(self.screen.widget, expand=True, fill=True, padding=0) self.title.set_mnemonic_widget( self.screen.current_view.mnemonic_widget) self.screen.widget.connect('key_press_event', self.on_keypress) self.wid_text.connect('key_press_event', self.on_keypress) def on_keypress(self, widget, event): editable = self.wid_text.get_editable() activate_keys = [Gdk.KEY_Tab, Gdk.KEY_ISO_Left_Tab] remove_keys = [Gdk.KEY_Delete, Gdk.KEY_KP_Delete] if not self.wid_completion: activate_keys.append(Gdk.KEY_Return) if widget == self.screen.widget: if event.keyval == Gdk.KEY_F3 and editable: self._sig_add() return True elif event.keyval == Gdk.KEY_F2: self._sig_edit() return True elif event.keyval in remove_keys and editable: self._sig_remove() return True elif widget == self.wid_text: if event.keyval == Gdk.KEY_F3: self._sig_new() return True elif event.keyval == Gdk.KEY_F2: self._sig_add() return True elif event.keyval in activate_keys and self.wid_text.get_text(): self._sig_add() self.wid_text.grab_focus() return False def destroy(self): self.wid_text.disconnect_by_func(self._focus_out) self.screen.destroy() def get_access(self, type_): model = self.attrs['relation'] if model: return common.MODELACCESS[model][type_] else: return True @property def read_access(self): return self.get_access('read') @property def create_access(self): return int(self.attrs.get('create', 1)) and self.get_access('create') def _sig_add(self, *args): if not self.focus_out: return domain = self.field.domain_get(self.record) add_remove = self.record.expr_eval(self.attrs.get('add_remove')) if add_remove: domain = [domain, add_remove] context = self.field.get_search_context(self.record) order = self.field.get_search_order(self.record) value = self.wid_text.get_text() self.focus_out = False def callback(result): self.focus_out = True if result: ids = [x[0] for x in result] self.screen.load(ids, modified=True) self.screen.set_cursor() self.wid_text.set_text('') win = WinSearch(self.attrs['relation'], callback, sel_multi=True, context=context, domain=domain, order=order, view_ids=self.attrs.get('view_ids', '').split(','), views_preload=self.attrs.get('views', {}), new=self.create_access, title=self.attrs.get('string')) win.screen.search_filter(quote(value)) win.show() def _sig_remove(self, *args): self.screen.remove(remove=True) def _on_activate(self): self._sig_edit() def _get_screen_form(self): domain = self.field.domain_get(self.record) add_remove = self.record.expr_eval(self.attrs.get('add_remove')) if add_remove: domain = [domain, add_remove] context = self.field.get_context(self.record) # Remove the first tree view as mode is form only view_ids = self.attrs.get('view_ids', '').split(',')[1:] model = self.attrs['relation'] breadcrumb = list(self.view.screen.breadcrumb) breadcrumb.append( self.attrs.get('string') or common.MODELNAME.get(model)) return Screen(model, domain=domain, view_ids=view_ids, mode=['form'], views_preload=self.attrs.get('views', {}), context=context, breadcrumb=breadcrumb) def _sig_edit(self): if not self.screen.current_record: return # Create a new screen that is not linked to the parent otherwise on the # save of the record will trigger the save of the parent screen = self._get_screen_form() screen.load([self.screen.current_record.id]) screen.current_record = screen.group.get(self.screen.current_record.id) def callback(result): if result: screen.current_record.save() added = 'id' in self.screen.current_record.modified_fields # Force a reload on next display self.screen.current_record.cancel() if added: self.screen.current_record.modified_fields.setdefault('id') # Force a display to clear the CellCache self.screen.display() WinForm(screen, callback) def _sig_new(self, defaults=None): screen = self._get_screen_form() defaults = defaults.copy() if defaults is not None else {} defaults['rec_name'] = self.wid_text.get_text() def callback(result): self.focus_out = True if result: record = screen.current_record self.screen.load([record.id], modified=True) self.wid_text.set_text('') self.wid_text.grab_focus() self.focus_out = False WinForm( screen, callback, new=True, save_current=True, defaults=defaults) def _readonly_set(self, value): self._readonly = value self._set_button_sensitive() self.wid_text.set_sensitive(not value) self.wid_text.set_editable(not value) self._set_label_state() def _required_set(self, value): self._required = value self._set_label_state() def _set_label_state(self): common.apply_label_attributes( self.title, self._readonly, self._required) def _set_button_sensitive(self): if self.record and self.field: field_size = self.record.expr_eval(self.attrs.get('size')) m2m_size = len(self.field.get_eval(self.record)) size_limit = (field_size is not None and m2m_size >= field_size >= 0) else: size_limit = False self.but_add.set_sensitive(bool( not self._readonly and not size_limit)) self.but_remove.set_sensitive(bool( not self._readonly and self._position)) def record_message(self, position, size, *args): self._position = position name = str(position) if position else '_' selected = len(self.screen.selected_records) if selected > 1: name += '#%i' % selected name = '(%s/%s)' % (name, common.humanize(size)) self.label.set_text(name) self._set_button_sensitive() def display(self): super(Many2Many, self).display() if not self.field: self.screen.new_group() self.screen.current_record = None self.screen.parent = None self.screen.display() return False new_group = self.field.get_client(self.record) if id(self.screen.group) != id(new_group): self.screen.group = new_group self.screen.display() return True def set_value(self): self.screen.current_view.set_value() return True def _completion_match_selected(self, completion, model, iter_): record_id, defaults = model.get(iter_, 1, 2) if record_id is not None: self.screen.load([record_id], modified=True) self.wid_text.set_text('') self.wid_text.grab_focus() completion_model = self.wid_completion.get_model() completion_model.clear() completion_model.search_text = self.wid_text.get_text() else: self._sig_new(defaults) return True def _update_completion(self, widget): if self._readonly: return if not self.record: return model = self.attrs['relation'] domain = self.field.domain_get(self.record) add_remove = self.record.expr_eval(self.attrs.get('add_remove')) if add_remove: domain = [domain, add_remove] update_completion( self.wid_text, self.record, self.field, model, domain) def _completion_action_activated(self, completion, index): if index == 0: self._sig_add() self.wid_text.grab_focus() elif index == 1: self._sig_new() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1707150267.0 tryton-7.0.24/tryton/gui/window/view_form/view/form_gtk/many2one.py0000644000175000017500000003413414560205673023603 0ustar00cedced# This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. import gettext from gi.repository import Gdk, GLib, Gtk import tryton.common as common from tryton.common.completion import get_completion, update_completion from tryton.common.domain_parser import quote from tryton.common.entry_position import reset_position from tryton.common.popup_menu import populate from tryton.gui.window.view_form.screen import Screen from tryton.gui.window.win_form import WinForm from tryton.gui.window.win_search import WinSearch from .widget import Widget _ = gettext.gettext class Many2One(Widget): default_width_chars = 12 def __init__(self, view, attrs): super(Many2One, self).__init__(view, attrs) self.widget = Gtk.HBox(spacing=0) self.widget.set_property('sensitive', True) self.wid_text = self.mnemonic_widget = Gtk.Entry() self.wid_text.set_property('width-chars', self.default_width_chars) self.wid_text.set_property('activates_default', True) self.wid_text.connect('key-press-event', self.send_modified) self.wid_text.connect('key_press_event', self.sig_key_press) self.wid_text.connect('populate-popup', self._populate_popup) self.wid_text.connect('focus-out-event', lambda x, y: self._focus_out()) self.wid_text.connect('changed', self.sig_changed) self.changed = True self.focus_out = True if int(self.attrs.get('completion', 1)): self.wid_text.connect('changed', self._update_completion) self.wid_completion = None self.wid_text.connect('icon-press', self.sig_edit) self.widget.pack_end(self.wid_text, expand=True, fill=True, padding=0) self._readonly = False def get_model(self): return self.attrs['relation'] def _readonly_set(self, value): self._readonly = value self._set_button_sensitive() def _set_button_sensitive(self): self.wid_text.set_editable(not self._readonly) self.wid_text.set_icon_sensitive( Gtk.EntryIconPosition.PRIMARY, self.read_access) self.wid_text.set_icon_sensitive( Gtk.EntryIconPosition.SECONDARY, not self._readonly) def get_access(self, type_): model = self.get_model() if model: return common.MODELACCESS[model][type_] else: return True @property def read_access(self): return self.get_access('read') @property def create_access(self): return int(self.attrs.get('create', 1)) and self.get_access('create') @property def modified(self): if self.record and self.field: value = self.wid_text.get_text() return self.field.get_client(self.record) != value return False @staticmethod def has_target(value): return value is not None @staticmethod def value_from_id(id_, str_=None): if str_ is None: str_ = '' return id_, str_ @staticmethod def id_from_value(value): return value def sig_activate(self): model = self.get_model() if not model or not common.MODELACCESS[model]['read']: return if not self.focus_out or not self.field: return self.changed = False value = self.field.get(self.record) model = self.get_model() self.focus_out = False if model and not self.has_target(value): if (not self._readonly and (self.wid_text.get_text() or self.field.get_state_attrs( self.record)['required'])): domain = self.field.domain_get(self.record) context = self.field.get_search_context(self.record) order = self.field.get_search_order(self.record) text = self.wid_text.get_text() def callback(result): if result: self.field.set_client(self.record, self.value_from_id(*result[0]), force_change=True) else: self.wid_text.set_text('') self.focus_out = True self.changed = True win = WinSearch(model, callback, sel_multi=False, context=context, domain=domain, order=order, view_ids=self.attrs.get('view_ids', '').split(','), views_preload=self.attrs.get('views', {}), new=self.create_access, title=self.attrs.get('string'), exclude_field=self.attrs.get('relation_field')) win.screen.search_filter(quote(text)) win.show() return self.focus_out = True self.changed = True return def get_screen(self, search=False): domain = self.field.domain_get(self.record) if search: context = self.field.get_search_context(self.record) else: context = self.field.get_context(self.record) # Remove first tree view as mode is form only view_ids = self.attrs.get('view_ids', '').split(',')[1:] model = self.get_model() breadcrumb = list(self.view.screen.breadcrumb) breadcrumb.append( self.attrs.get('string') or common.MODELNAME.get(model)) return Screen(model, domain=domain, context=context, mode=['form'], view_ids=view_ids, views_preload=self.attrs.get('views', {}), readonly=self._readonly, exclude_field=self.attrs.get('relation_field'), breadcrumb=breadcrumb) def sig_new(self, defaults=None): if not self.create_access: return self.focus_out = False screen = self.get_screen(search=True) defaults = defaults.copy() if defaults is not None else {} defaults['rec_name'] = self.wid_text.get_text() def callback(result): if result: self.field.set_client(self.record, self.value_from_id(screen.current_record.id, screen.current_record.rec_name())) self.focus_out = True WinForm( screen, callback, new=True, save_current=True, defaults=defaults) def sig_edit(self, entry=None, icon_pos=None, *args): if entry: entry.grab_focus() model = self.get_model() if not model or not common.MODELACCESS[model]['read']: return if not self.focus_out or not self.field: return self.changed = False self.focus_out = False value = self.field.get(self.record) if (icon_pos == Gtk.EntryIconPosition.SECONDARY and not self._readonly and self.has_target(value)): self.field.set_client(self.record, self.value_from_id(None, '')) self.wid_text.set_text('') self.changed = True self.focus_out = True return if self.has_target(value): m2o_id = self.id_from_value(self.field.get(self.record)) screen = self.get_screen() screen.load([m2o_id]) screen.current_record = screen.group.get(m2o_id) def callback(result): if result: self.field.set_client(self.record, self.value_from_id(screen.current_record.id, screen.current_record.rec_name()), force_change=True) self.focus_out = True self.changed = True WinForm(screen, callback, save_current=True) return if not self._readonly: domain = self.field.domain_get(self.record) context = self.field.get_search_context(self.record) order = self.field.get_search_order(self.record) text = self.wid_text.get_text() def callback(result): if result: self.field.set_client(self.record, self.value_from_id(*result[0]), force_change=True) self.focus_out = True self.changed = True win = WinSearch(model, callback, sel_multi=False, context=context, domain=domain, order=order, view_ids=self.attrs.get('view_ids', '').split(','), views_preload=self.attrs.get('views', {}), new=self.create_access, title=self.attrs.get('string'), exclude_field=self.attrs.get('relation_field')) win.screen.search_filter(quote(text)) win.show() return self.focus_out = True self.changed = True def sig_key_press(self, widget, event, *args): editable = self.wid_text.get_editable() activate_keys = [Gdk.KEY_Tab, Gdk.KEY_ISO_Left_Tab] if not self.wid_completion: activate_keys.append(Gdk.KEY_Return) if (event.keyval == Gdk.KEY_F3 and editable and self.create_access): self.sig_new() return True elif event.keyval == Gdk.KEY_F2 and self.read_access: self.sig_edit(widget) return True elif (event.keyval in activate_keys and editable): self.sig_activate() elif (self.has_target(self.field.get(self.record)) and editable and event.keyval in [Gdk.KEY_Delete, Gdk.KEY_BackSpace]): self.wid_text.set_text('') return False def sig_changed(self, *args): if not self.changed: return False value = self.field.get(self.record) if self.has_target(value) and self.modified: def clean(): if not self.wid_text.props.window: return text = self.wid_text.get_text() position = self.wid_text.get_position() self.field.set_client(self.record, self.value_from_id(None, '')) # The value of the field could be different of None # in such case, the original text should not be restored if not self.wid_text.get_text(): # Restore text and position after display self.wid_text.set_text(text) self.wid_text.set_position(position) GLib.idle_add(clean) return False def get_value(self): return self.wid_text.get_text() def set_value(self): if self.field.get_client(self.record) != self.wid_text.get_text(): self.field.set_client(self.record, self.value_from_id(None, '')) self.wid_text.set_text('') def set_text(self, value): if not value: value = '' self.wid_text.set_text(value) reset_position(self.wid_text) def display(self): self.changed = False super(Many2One, self).display() self._set_button_sensitive() self._set_completion() if not self.field: self.set_text(None) self.changed = True return False self.set_text(self.field.get_client(self.record)) if self.has_target(self.field.get(self.record)): icon1, tooltip1 = 'tryton-open', _('Open the record ') icon2, tooltip2 = 'tryton-clear', _('Clear the field ') else: icon1, tooltip1 = None, '' icon2, tooltip2 = 'tryton-search', _('Search a record ') if not self.wid_text.get_editable(): icon2, tooltip2 = None, '' for pos, icon, tooltip in [ (Gtk.EntryIconPosition.PRIMARY, icon1, tooltip1), (Gtk.EntryIconPosition.SECONDARY, icon2, tooltip2)]: if icon: pixbuf = common.IconFactory.get_pixbuf( icon, Gtk.IconSize.MENU) else: pixbuf = None self.wid_text.set_icon_from_pixbuf(pos, pixbuf) self.wid_text.set_icon_tooltip_text(pos, tooltip) self.changed = True def _populate_popup(self, widget, menu): value = self.field.get(self.record) if self.has_target(value): populate( menu, self.get_model(), self.id_from_value(value), '', self.field, self.field.get_context(self.record)) return True def _set_completion(self): if not int(self.attrs.get('completion', 1)): return self.wid_completion = get_completion( search=self.read_access, create=self.create_access) self.wid_completion.connect('match-selected', self._completion_match_selected) self.wid_completion.connect('action-activated', self._completion_action_activated) self.wid_text.set_completion(self.wid_completion) def _completion_match_selected(self, completion, model, iter_): rec_name, record_id, defaults = model.get(iter_, 0, 1, 2) if record_id is not None: # GTK on win32 doesn't like synchronous call to set_client # because it triggers a display which reset the completion GLib.idle_add(self.field.set_client, self.record, self.value_from_id(record_id, rec_name), True) completion_model = self.wid_completion.get_model() completion_model.clear() completion_model.search_text = rec_name else: self.sig_new(defaults) return True def _update_completion(self, widget): if self._readonly: return if not self.record: return value = self.field.get(self.record) if self.has_target(value): id_ = self.id_from_value(value) if id_ is not None and id_ >= 0: return model = self.get_model() update_completion(self.wid_text, self.record, self.field, model) def _completion_action_activated(self, completion, index): if index == 0: self.sig_edit() elif index == 1: self.sig_new() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685599.0 tryton-7.0.24/tryton/gui/window/view_form/view/form_gtk/multiselection.py0000644000175000017500000000715214517761237025120 0ustar00cedced# This file is part of Tryton. The COPYRIGHT file at the top level of this # repository contains the full copyright notices and license terms. from gi.repository import GLib, GObject, Gtk from tryton.common.selection import SelectionMixin from .widget import Widget class MultiSelection(Widget, SelectionMixin): expand = True def __init__(self, view, attrs): super(MultiSelection, self).__init__(view, attrs) if int(attrs.get('yexpand', self.expand)): self.widget = Gtk.ScrolledWindow() self.widget.set_policy( Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC) self.widget.set_shadow_type(Gtk.ShadowType.ETCHED_IN) else: self.widget = Gtk.VBox() self.widget.set_size_request(100, 100) self.widget.get_accessible().set_name(attrs.get('string', '')) self.model = Gtk.ListStore(GObject.TYPE_PYOBJECT, GObject.TYPE_STRING) self.tree = self.mnemonic_widget = Gtk.TreeView() self.tree.set_model(self.model) self.tree.set_search_column(1) self.tree.connect('focus-out-event', lambda *a: self._focus_out()) self.tree.set_headers_visible(False) selection = self.tree.get_selection() selection.set_mode(Gtk.SelectionMode.MULTIPLE) selection.connect('changed', self.changed) self.widget.add(self.tree) name_column = Gtk.TreeViewColumn() name_cell = Gtk.CellRendererText() name_column.pack_start(name_cell, expand=True) name_column.add_attribute(name_cell, 'text', 1) self.tree.append_column(name_column) self.nullable_widget = False self.init_selection() def _readonly_set(self, readonly): super(MultiSelection, self)._readonly_set(readonly) selection = self.tree.get_selection() selection.set_select_function(lambda *a: not readonly) @property def modified(self): if self.record and self.field: group = set(self.field.get_eval(self.record)) value = set(self.get_value()) return value != group return False def changed(self, selection): def focus_out(): if self.widget.props.window: self._focus_out() # Must be deferred because it triggers a display of the form GLib.idle_add(focus_out) def get_value(self): model, paths = self.tree.get_selection().get_selected_rows() return [model[path][0] for path in paths] def set_value(self): self.field.set_client(self.record, self.get_value()) def display(self): selection = self.tree.get_selection() selection.handler_block_by_func(self.changed) try: # Remove select_function to allow update, # it will be set back in the super call selection.set_select_function(lambda *a: True) self.update_selection(self.record, self.field) new_model = self.selection != [list(row) for row in self.model] if new_model: self.model.clear() if not self.field: return value2path = {} for idx, (value, name) in enumerate(self.selection): if new_model: self.model.append((value, name)) value2path[value] = idx selection.unselect_all() values = self.field.get_eval(self.record) for value in values: selection.select_path(value2path[value]) super(MultiSelection, self).display() finally: selection.handler_unblock_by_func(self.changed) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1707150267.0 tryton-7.0.24/tryton/gui/window/view_form/view/form_gtk/one2many.py0000644000175000017500000005466614560205673023617 0ustar00cedced# This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. import gettext import itertools from gi.repository import Gdk, Gtk import tryton.common as common from tryton.common.completion import get_completion, update_completion from tryton.common.domain_parser import quote from tryton.common.underline import set_underline from tryton.gui.window.view_form.screen import Screen from tryton.gui.window.win_form import WinForm from tryton.gui.window.win_search import WinSearch from .widget import Widget _ = gettext.gettext class One2Many(Widget): expand = True def __init__(self, view, attrs): super(One2Many, self).__init__(view, attrs) self.widget = Gtk.Frame() self.widget.set_shadow_type(Gtk.ShadowType.NONE) self.widget.get_accessible().set_name(attrs.get('string', '')) vbox = Gtk.VBox(homogeneous=False, spacing=2) self.widget.add(vbox) self._readonly = True self._required = False self._position = 0 self._length = 0 self.title_box = hbox = Gtk.HBox(homogeneous=False, spacing=0) hbox.set_border_width(2) self.title = Gtk.Label( label=set_underline(attrs.get('string', '')), use_underline=True, halign=Gtk.Align.START) hbox.pack_start(self.title, expand=True, fill=True, padding=0) hbox.pack_start(Gtk.VSeparator(), expand=False, fill=True, padding=0) tooltips = common.Tooltips() self.but_switch = Gtk.Button(can_focus=False) tooltips.set_tip(self.but_switch, _('Switch')) self.but_switch.connect('clicked', self.switch_view) self.but_switch.add(common.IconFactory.get_image( 'tryton-switch', Gtk.IconSize.SMALL_TOOLBAR)) self.but_switch.set_relief(Gtk.ReliefStyle.NONE) hbox.pack_start(self.but_switch, expand=False, fill=False, padding=0) self.but_pre = Gtk.Button(can_focus=False) tooltips.set_tip(self.but_pre, _('Previous')) self.but_pre.connect('clicked', self._sig_previous) self.but_pre.add(common.IconFactory.get_image( 'tryton-back', Gtk.IconSize.SMALL_TOOLBAR)) self.but_pre.set_relief(Gtk.ReliefStyle.NONE) hbox.pack_start(self.but_pre, expand=False, fill=False, padding=0) self.label = Gtk.Label(label='(_/0)') hbox.pack_start(self.label, expand=False, fill=False, padding=0) self.but_next = Gtk.Button(can_focus=False) tooltips.set_tip(self.but_next, _('Next')) self.but_next.connect('clicked', self._sig_next) self.but_next.add(common.IconFactory.get_image( 'tryton-forward', Gtk.IconSize.SMALL_TOOLBAR)) self.but_next.set_relief(Gtk.ReliefStyle.NONE) hbox.pack_start(self.but_next, expand=False, fill=False, padding=0) hbox.pack_start(Gtk.VSeparator(), expand=False, fill=True, padding=0) self.focus_out = True self.wid_completion = None if attrs.get('add_remove'): self.wid_text = Gtk.Entry() self.wid_text.set_placeholder_text(_('Search')) self.wid_text.set_property('width_chars', 13) self.wid_text.connect('focus-out-event', self._focus_out) hbox.pack_start(self.wid_text, expand=True, fill=True, padding=0) if int(self.attrs.get('completion', 1)): self.wid_completion = get_completion( search=self.read_access, create=self.create_access) self.wid_completion.connect('match-selected', self._completion_match_selected) self.wid_completion.connect('action-activated', self._completion_action_activated) self.wid_text.set_completion(self.wid_completion) self.wid_text.connect('changed', self._update_completion) self.but_add = Gtk.Button(can_focus=False) tooltips.set_tip(self.but_add, _('Add existing record')) self.but_add.connect('clicked', self._sig_add) self.but_add.add(common.IconFactory.get_image( 'tryton-add', Gtk.IconSize.SMALL_TOOLBAR)) self.but_add.set_relief(Gtk.ReliefStyle.NONE) hbox.pack_start(self.but_add, expand=False, fill=False, padding=0) self.but_remove = Gtk.Button(can_focus=False) tooltips.set_tip(self.but_remove, _('Remove selected record')) self.but_remove.connect('clicked', self._sig_remove, True) self.but_remove.add(common.IconFactory.get_image( 'tryton-remove', Gtk.IconSize.SMALL_TOOLBAR)) self.but_remove.set_relief(Gtk.ReliefStyle.NONE) hbox.pack_start( self.but_remove, expand=False, fill=False, padding=0) hbox.pack_start( Gtk.VSeparator(), expand=False, fill=True, padding=0) self.but_new = Gtk.Button(can_focus=False) tooltips.set_tip(self.but_new, _('Create a new record')) self.but_new.connect('clicked', lambda *a: self._sig_new()) self.but_new.add(common.IconFactory.get_image( 'tryton-create', Gtk.IconSize.SMALL_TOOLBAR)) self.but_new.set_relief(Gtk.ReliefStyle.NONE) hbox.pack_start(self.but_new, expand=False, fill=False, padding=0) self.but_open = Gtk.Button(can_focus=False) tooltips.set_tip(self.but_open, _('Edit selected record')) self.but_open.connect('clicked', self._sig_edit) self.but_open.add(common.IconFactory.get_image( 'tryton-open', Gtk.IconSize.SMALL_TOOLBAR)) self.but_open.set_relief(Gtk.ReliefStyle.NONE) hbox.pack_start(self.but_open, expand=False, fill=False, padding=0) self.but_del = Gtk.Button(can_focus=False) tooltips.set_tip(self.but_del, _('Delete selected record')) self.but_del.connect('clicked', self._sig_remove, False) self.but_del.add(common.IconFactory.get_image( 'tryton-delete', Gtk.IconSize.SMALL_TOOLBAR)) self.but_del.set_relief(Gtk.ReliefStyle.NONE) hbox.pack_start(self.but_del, expand=False, fill=False, padding=0) self.but_undel = Gtk.Button(can_focus=False) tooltips.set_tip(self.but_undel, _('Undelete selected record ')) self.but_undel.connect('clicked', self._sig_undelete) self.but_undel.add(common.IconFactory.get_image( 'tryton-undo', Gtk.IconSize.SMALL_TOOLBAR)) self.but_undel.set_relief(Gtk.ReliefStyle.NONE) hbox.pack_start(self.but_undel, expand=False, fill=False, padding=0) tooltips.enable() frame = Gtk.Frame() frame.add(hbox) frame.set_shadow_type(Gtk.ShadowType.OUT) vbox.pack_start(frame, expand=False, fill=True, padding=0) model = attrs['relation'] breadcrumb = list(self.view.screen.breadcrumb) breadcrumb.append( attrs.get('string') or common.MODELNAME.get(model)) self.screen = Screen(model, mode=attrs.get('mode', 'tree,form').split(','), view_ids=attrs.get('view_ids', '').split(','), views_preload=attrs.get('views', {}), order=attrs.get('order'), row_activate=self._on_activate, exclude_field=attrs.get('relation_field', None), limit=None, context=self.view.screen.context, breadcrumb=breadcrumb) self.screen.pre_validate = bool(int(attrs.get('pre_validate', 0))) self.screen.windows.append(self) vbox.pack_start(self.screen.widget, expand=True, fill=True, padding=0) self.title.set_mnemonic_widget( self.screen.current_view.mnemonic_widget) self.screen.widget.connect('key_press_event', self.on_keypress) if self.attrs.get('add_remove'): self.wid_text.connect('key_press_event', self.on_keypress) def get_access(self, type_): model = self.attrs['relation'] if model: return common.MODELACCESS[model][type_] else: return True @property def read_access(self): return self.get_access('read') @property def create_access(self): return int(self.attrs.get('create', 1)) and self.get_access('create') @property def write_access(self): return self.get_access('write') @property def delete_access(self): return int(self.attrs.get('delete', 1)) and self.get_access('delete') def on_keypress(self, widget, event): if ((event.keyval == Gdk.KEY_F3) and self.but_new.get_property('sensitive')): self._sig_new() return True if event.keyval == Gdk.KEY_F2: if widget == self.screen.widget: self._sig_edit(widget) return True elif widget == self.wid_text: self._sig_add(widget) return True if event.keyval == Gdk.KEY_F4: self.switch_view(widget) if (event.keyval in [Gdk.KEY_Delete, Gdk.KEY_KP_Delete] and widget == self.screen.widget): remove = not (event.state & Gdk.ModifierType.CONTROL_MASK) if remove and self.attrs.get('add_remove'): but = self.but_remove else: remove = False but = self.but_del if but.get_property('sensitive'): self._sig_remove(widget, remove) return True if event.keyval == Gdk.KEY_Insert and widget == self.screen.widget: self._sig_undelete(widget) return True if self.attrs.get('add_remove'): editable = self.wid_text.get_editable() activate_keys = [Gdk.KEY_Tab, Gdk.KEY_ISO_Left_Tab] if not self.wid_completion: activate_keys.append(Gdk.KEY_Return) if (widget == self.wid_text and event.keyval in activate_keys and editable and self.wid_text.get_text()): self._sig_add() self.wid_text.grab_focus() return False def destroy(self): if self.attrs.get('add_remove'): self.wid_text.disconnect_by_func(self._focus_out) self.screen.destroy() def _on_activate(self): self._sig_edit() def switch_view(self, widget): self.screen.switch_view() mnemonic_widget = self.screen.current_view.mnemonic_widget string = self.attrs.get('string', '') if mnemonic_widget: string = set_underline(string) self.title.set_mnemonic_widget(mnemonic_widget) self.title.set_label(string) @property def modified(self): return self.screen.current_view.modified def _readonly_set(self, value): self._readonly = value self._set_button_sensitive() self._set_label_state() def _required_set(self, value): self._required = value self._set_label_state() def _set_label_state(self): common.apply_label_attributes( self.title, self._readonly, self._required) def _set_button_sensitive(self): if self.record and self.field: field_size = self.record.expr_eval(self.attrs.get('size')) o2m_size = len(self.field.get_eval(self.record)) size_limit = (field_size is not None and o2m_size >= field_size >= 0) else: o2m_size = None size_limit = False first = last = False if isinstance(self._position, int): first = self._position <= 1 last = self._position >= self._length deletable = self.screen.deletable view_type = self.screen.current_view.view_type has_views = self.screen.number_of_views > 1 self.but_switch.set_sensitive( (self._position or view_type == 'form') and has_views) self.but_new.set_sensitive(bool( not self._readonly and self.create_access and not size_limit)) self.but_del.set_sensitive(bool( not self._readonly and self.delete_access and deletable and self._position)) self.but_undel.set_sensitive(bool( not self._readonly and not size_limit and self._position)) self.but_open.set_sensitive(bool( self._position and self.read_access)) self.but_next.set_sensitive(bool( self._position and not last)) self.but_pre.set_sensitive(bool( self._position and not first)) if self.attrs.get('add_remove'): self.but_add.set_sensitive(bool( not self._readonly and not size_limit and self.write_access and self.read_access)) self.but_remove.set_sensitive(bool( not self._readonly and self._position and self.write_access and self.read_access)) self.wid_text.set_sensitive(self.but_add.get_sensitive()) self.wid_text.set_editable(self.but_add.get_sensitive()) def _validate(self): self.view.set_value() record = self.screen.current_record if record: fields = self.screen.current_view.get_fields() if not record.validate(fields): self.screen.display(set_cursor=True) return False if self.screen.pre_validate and not record.pre_validate(): return False return True def _sequence(self): for view in self.screen.views: if view.view_type == 'tree': sequence = view.attributes.get('sequence') if sequence: return sequence def _sig_new(self, defaults=None): if not self.create_access: return if not self._validate(): return if self.attrs.get('add_remove'): defaults = defaults.copy() if defaults is not None else {} defaults['rec_name'] = self.wid_text.get_text() if self.attrs.get('product'): self._new_product(defaults) else: self._new_single(defaults) def _new_single(self, defaults=None): sequence = self._sequence() def update_sequence(): if sequence: self.screen.group.set_sequence( field=sequence, position=self.screen.new_position) if self.screen.current_view.creatable: self.screen.new() self.screen.current_view.widget.set_sensitive(True) update_sequence() else: field_size = self.record.expr_eval(self.attrs.get('size')) or -1 field_size -= len(self.field.get_eval(self.record)) + 1 WinForm( self.screen, lambda a: update_sequence(), new=True, defaults=defaults, many=field_size) def _new_product(self, defaults=None): fields = self.attrs['product'].split(',') product = {} first = self.screen.new(default=False) default = first.default_get(defaults=defaults) first.set_default(default) def search_set(*args): if not fields: return make_product() field = self.screen.group.fields[fields.pop()] relation = field.attrs.get('relation') if not relation: search_set() domain = field.domain_get(first) context = field.get_search_context(first) order = field.get_search_order(first) def callback(result): if result: product[field.name] = result win_search = WinSearch(relation, callback, sel_multi=True, context=context, domain=domain, order=order, title=self.attrs.get('string')) win_search.win.connect('destroy', search_set) win_search.screen.search_filter() win_search.show() def make_product(): self.screen.group.remove(first, remove=True) if not product: return fields = list(product.keys()) for values in itertools.product(*list(product.values())): record = self.screen.new(default=False) default_value = default.copy() for field, value in zip(fields, values): id_, rec_name = value default_value[field] = id_ default_value[field + '.rec_name'] = rec_name record.set_default(default_value) sequence = self._sequence() if sequence: self.screen.group.set_sequence( field=sequence, position=self.screen.new_position) search_set() def _sig_edit(self, widget=None): if not common.MODELACCESS[self.screen.model_name]['read']: return if not self._validate(): return record = self.screen.current_record if record: WinForm(self.screen, lambda a: None) def _sig_next(self, widget): if not self._validate(): return self.screen.display_next() def _sig_previous(self, widget): if not self._validate(): return self.screen.display_prev() def _sig_remove(self, widget, remove=False): writable = not self.screen.readonly deletable = self.screen.deletable if remove: if not self.write_access or not writable or not self.read_access: return else: if not self.delete_access or not deletable: return self.screen.remove(remove=remove) def _sig_undelete(self, button): self.screen.unremove() def _sig_add(self, *args): if not self.focus_out: return if not self.write_access or not self.read_access: return self.view.set_value() domain = self.field.domain_get(self.record) context = self.field.get_search_context(self.record) domain = [domain, self.record.expr_eval(self.attrs.get('add_remove'))] removed_ids = self.field.get_removed_ids(self.record) domain = ['OR', domain, ('id', 'in', removed_ids)] text = self.wid_text.get_text() self.focus_out = False sequence = self._sequence() def callback(result): self.focus_out = True if result: ids = [x[0] for x in result] self.screen.load(ids, modified=True) if sequence: self.screen.group.set_sequence( field=sequence, position=self.screen.new_position) self.screen.set_cursor() self.wid_text.set_text('') order = self.field.get_search_order(self.record) win = WinSearch(self.attrs['relation'], callback, sel_multi=True, context=context, domain=domain, order=order, view_ids=self.attrs.get('view_ids', '').split(','), views_preload=self.attrs.get('views', {}), new=self.but_new.get_property('sensitive'), title=self.attrs.get('string'), exclude_field=self.attrs.get('relation_field')) win.screen.search_filter(quote(text)) win.show() def record_message(self, position, size, *args): self._position = position self._length = size name = str(position) if position else '_' selected = len(self.screen.selected_records) if selected > 1: name += '#%i' % selected name = '(%s/%s)' % (name, common.humanize(size)) self.label.set_text(name) self._set_button_sensitive() def display(self): super(One2Many, self).display() self._set_button_sensitive() if not self.field: self.screen.new_group() self.screen.current_record = None self.screen.parent = None self.screen.display() return False new_group = self.field.get_client(self.record) if id(self.screen.group) != id(new_group): self.screen.group = new_group if (self.screen.current_view.view_type == 'form' and self.screen.group): self.screen.current_record = self.screen.group[0] domain = [] size_limit = None if self.record: domain = self.field.domain_get(self.record) size_limit = self.record.expr_eval(self.attrs.get('size')) if self._readonly or not self.create_access: if size_limit is None: size_limit = len(self.screen.group) else: size_limit = min(size_limit, len(self.screen.group)) if self.screen.domain != domain: self.screen.domain = domain self.screen.size_limit = size_limit self.screen.display() return True def set_value(self): self.screen.current_view.set_value() if self.screen.modified(): # TODO check if required self.view.screen.record_modified(display=False) return True def _completion_match_selected(self, completion, model, iter_): record_id, defaults = model.get(iter_, 1, 2) if record_id is not None: self.screen.load([record_id], modified=True) self.wid_text.set_text('') self.wid_text.grab_focus() completion_model = self.wid_completion.get_model() completion_model.clear() completion_model.search_text = self.wid_text.get_text() else: self._sig_new(defaults) return True def _update_completion(self, widget): if self._readonly: return if not self.record: return model = self.attrs['relation'] domain = self.field.domain_get(self.record) domain = [domain, self.record.expr_eval(self.attrs.get('add_remove'))] removed_ids = self.field.get_removed_ids(self.record) domain = ['OR', domain, ('id', 'in', removed_ids)] update_completion(self.wid_text, self.record, self.field, model, domain=domain) def _completion_action_activated(self, completion, index): if index == 0: self._sig_add() self.wid_text.grab_focus() elif index == 1: self._sig_new() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685599.0 tryton-7.0.24/tryton/gui/window/view_form/view/form_gtk/one2one.py0000644000175000017500000000032314517761237023416 0ustar00cedced# This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. from .many2one import Many2One class One2One(Many2One): pass ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685599.0 tryton-7.0.24/tryton/gui/window/view_form/view/form_gtk/progressbar.py0000644000175000017500000000256514517761237024414 0ustar00cedced# This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. import gettext from gi.repository import Gtk from .widget import Widget _ = gettext.gettext class ProgressBar(Widget): 'Progress Bar' orientations = { 'left_to_right': (Gtk.Orientation.HORIZONTAL, False), 'right_to_left': (Gtk.Orientation.HORIZONTAL, True), 'bottom_to_top': (Gtk.Orientation.VERTICAL, True), 'top_to_bottom': (Gtk.Orientation.VERTICAL, False), } def __init__(self, view, attrs): super(ProgressBar, self).__init__(view, attrs) self.widget = self.mnemonic_widget = Gtk.ProgressBar() orientation, inverted = self.orientations.get( attrs.get('orientation', 'left_to_right')) self.widget.set_orientation(orientation) self.widget.set_inverted(inverted) self.widget.set_show_text(True) def display(self): super(ProgressBar, self).display() if not self.field: self.widget.set_text('') self.widget.set_fraction(0.0) return False text = self.field.get_client(self.record, factor=100) if text: text = _('%s%%') % text self.widget.set_text(text) value = self.field.get(self.record) or 0.0 self.widget.set_fraction(value) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685599.0 tryton-7.0.24/tryton/gui/window/view_form/view/form_gtk/pyson.py0000644000175000017500000000333714517761237023231 0ustar00cedced# This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. from gi.repository import Gtk from tryton.common import IconFactory from tryton.pyson import CONTEXT, PYSONDecoder, PYSONEncoder from .char import Char class PYSON(Char): def __init__(self, view, attrs): super(PYSON, self).__init__(view, attrs) self.encoder = PYSONEncoder() self.decoder = PYSONDecoder(noeval=True) self.entry.connect('key-release-event', self.validate_pyson) def get_encoded_value(self): value = self.get_value() if not value: return value try: return self.encoder.encode(eval(value, CONTEXT)) except Exception: return None def set_value(self): # avoid modification because different encoding value = self.get_encoded_value() previous = self.field.get_client(self.record) if (previous and value == self.encoder.encode( self.decoder.decode(previous))): value = previous self.field.set_client(self.record, value) def get_client_value(self): value = super(PYSON, self).get_client_value() if value: value = repr(self.decoder.decode(value)) return value def validate_pyson(self, *args): icon = 'tryton-ok' if self.get_encoded_value() is None: icon = 'tryton-error' pixbuf = IconFactory.get_pixbuf(icon, Gtk.IconSize.MENU) self.entry.set_icon_from_pixbuf( Gtk.EntryIconPosition.SECONDARY, pixbuf) def _focus_out(self): self.validate_pyson() super(PYSON, self)._focus_out() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685599.0 tryton-7.0.24/tryton/gui/window/view_form/view/form_gtk/reference.py0000644000175000017500000001152414517761237024014 0ustar00cedced# This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. import gettext from gi.repository import Gtk from tryton.common.selection import ( PopdownMixin, SelectionMixin, selection_shortcuts) from .many2one import Many2One _ = gettext.gettext class Reference(Many2One, SelectionMixin, PopdownMixin): def __init__(self, view, attrs): super(Reference, self).__init__(view, attrs) self.widget_combo = Gtk.ComboBox(has_entry=True) child = self.widget_combo.get_child() child.connect('activate', lambda *a: self._focus_out()) child.connect('focus-out-event', lambda *a: self._focus_out()) child.get_accessible().set_name(attrs.get('string', '')) self.widget_combo.connect('changed', self.sig_changed_combo) self.widget_combo.connect('move-active', self._move_active) self.widget_combo.connect( 'scroll-event', lambda c, e: c.stop_emission_by_name('scroll-event')) selection_shortcuts(self.widget_combo) self.widget.pack_start( self.widget_combo, expand=False, fill=True, padding=0) self.init_selection() self.set_popdown(self.selection, self.widget_combo) def get_model(self): active = self.widget_combo.get_active() if active < 0: return '' else: model = self.widget_combo.get_model() return model[active][1] def get_empty_value(self): for name, model in self.widget_combo.get_model(): if model in (None, ''): return model, name return '', '' def _move_active(self, combobox, scroll_type): if not combobox.get_child().get_editable(): combobox.stop_emission_by_name('move-active') def _set_button_sensitive(self): super(Reference, self)._set_button_sensitive() self.widget_combo.get_child().set_editable(not self._readonly) self.widget_combo.set_button_sensitivity( Gtk.SensitivityType.OFF if self._readonly else Gtk.SensitivityType.AUTO) @property def modified(self): if self.record and self.field: try: model, name = self.field.get_client(self.record) except (ValueError, TypeError): model, name = self.get_empty_value() return (model != self.get_model() or name != self.wid_text.get_text()) return False def has_target(self, value): if value is None: return False model, value = value.split(',') if not value: value = None else: try: value = int(value) except ValueError: value = None result = model == self.get_model() and value >= 0 return result def value_from_id(self, id_, str_=None): if str_ is None: str_ = '' return self.get_model(), (id_, str_) @staticmethod def id_from_value(value): _, value = value.split(',') return int(value) def sig_changed_combo(self, *args): if not self.changed: return self.wid_text.set_text('') model = self.get_model() if model: value = (model, (-1, '')) else: value = ('', '') self.field.set_client(self.record, value) def set_value(self): if not self.get_model(): value = self.wid_text.get_text() if not value: self.field.set_client(self.record, None) else: self.field.set_client(self.record, ('', value)) return else: try: model, name = self.field.get_client(self.record) except (ValueError, TypeError): model, name = self.get_empty_value() if (model != self.get_model() or name != self.wid_text.get_text()): self.field.set_client(self.record, None) self.set_text(None) def set_text(self, value): if value: model, value = value else: model, value = None, None super(Reference, self).set_text(value) self.widget_combo.handler_block_by_func(self.sig_changed_combo) if not self.set_popdown_value(self.widget_combo, model): text = self.get_inactive_selection(model) self.set_popdown( self.selection[:] + [(model, text)], self.widget_combo) self.set_popdown_value(self.widget_combo, value) self.widget_combo.handler_unblock_by_func(self.sig_changed_combo) def display(self): self.update_selection(self.record, self.field) self.set_popdown(self.selection, self.widget_combo) super(Reference, self).display() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685599.0 tryton-7.0.24/tryton/gui/window/view_form/view/form_gtk/richtextbox.py0000644000175000017500000000537714517761237024432 0ustar00cedced# This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. from weakref import WeakKeyDictionary from gi.repository import Gtk from tryton.common.htmltextbuffer import normalize_markup from tryton.common.richtext import ( add_toolbar, get_content, register_format, set_content) from tryton.config import CONFIG from .textbox import TextBox class RichTextBox(TextBox): def __init__(self, view, attrs): super(RichTextBox, self).__init__(view, attrs) self.toolbar = None self.tag_widgets = WeakKeyDictionary() self.tags = {} self.colors = {} if int(self.attrs.get('toolbar', 1)): self.toolbar = add_toolbar(self.textview) self.toolbar.set_style({ 'default': False, 'both': Gtk.ToolbarStyle.BOTH, 'text': Gtk.ToolbarStyle.TEXT, 'icons': Gtk.ToolbarStyle.ICONS, }[CONFIG['client.toolbar']]) self.widget.pack_start( self.toolbar, expand=False, fill=True, padding=0) def _get_textview(self): textview = super(RichTextBox, self)._get_textview() register_format(textview) return textview def translate_widget(self): widget = super(RichTextBox, self).translate_widget() textview = widget.get_children()[-1].get_child() if self.toolbar: widget.pack_start( add_toolbar(textview), expand=False, fill=True, padding=0) return widget def translate_widget_set_readonly(self, widget, value): super(RichTextBox, self).translate_widget_set_readonly(widget, value) if self.toolbar: toolbar = widget.get_children()[0] for n in range(toolbar.get_n_items()): tool = toolbar.get_nth_item(n) tool.set_sensitive(not value) def set_value(self): # avoid modification of not normalized value value = self.get_value() prev_value = self.field.get_client(self.record) or '' if value == normalize_markup(prev_value): value = prev_value self.field.set_client(self.record, value) @property def modified(self): if self.record and self.field: value = normalize_markup(self.field.get_client(self.record) or '') return value != self.get_value() return False def set_buffer(self, value, textview): set_content(textview, value) def get_buffer(self, textview): return get_content(textview) def _readonly_set(self, value): super(RichTextBox, self)._readonly_set(value) if self.toolbar: self.toolbar.set_sensitive(not value) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685599.0 tryton-7.0.24/tryton/gui/window/view_form/view/form_gtk/selection.py0000644000175000017500000000666614517761237024056 0ustar00cedced# This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. from gi.repository import GLib, Gtk from tryton.common.selection import ( PopdownMixin, SelectionMixin, selection_shortcuts) from .widget import Widget class Selection(Widget, SelectionMixin, PopdownMixin): def __init__(self, view, attrs): super(Selection, self).__init__(view, attrs) self.widget = Gtk.HBox(spacing=3) self.entry = Gtk.ComboBox(has_entry=True) child = self.mnemonic_widget = self.entry.get_child() child.set_property('activates_default', True) child.set_max_length(int(attrs.get('size', 0))) child.set_width_chars(self.default_width_chars) selection_shortcuts(self.entry) child.connect('activate', lambda *a: self._focus_out()) child.connect('focus-out-event', lambda *a: self._focus_out()) self.entry.connect('changed', self.changed) self.entry.connect('move-active', self._move_active) self.entry.connect( 'scroll-event', lambda c, e: c.stop_emission_by_name('scroll-event')) self.widget.pack_start(self.entry, expand=True, fill=True, padding=0) self.selection = attrs.get('selection', [])[:] self.attrs = attrs self.init_selection() self.set_popdown(self.selection, self.entry) def changed(self, combobox): def focus_out(): if combobox.props.window: self._focus_out() # Must be deferred because it triggers a display of the form GLib.idle_add(focus_out) def _move_active(self, combobox, scroll_type): if not combobox.get_child().get_editable(): combobox.stop_emission_by_name('move-active') def _readonly_set(self, value): super(Selection, self)._readonly_set(value) self.entry.get_child().set_editable(not value) self.entry.set_button_sensitivity( Gtk.SensitivityType.OFF if value else Gtk.SensitivityType.AUTO) def get_value(self): if not self.entry.get_child(): # entry is destroyed return return self.get_popdown_value(self.entry) @property def modified(self): if self.record and self.field: return self.field.get(self.record) != self.get_value() return False def set_value(self): value = self.get_value() if 'relation' in self.attrs and value: value = (value, self.get_popdown_text(self.entry)) self.field.set_client(self.record, value) def display(self): self.update_selection(self.record, self.field) self.set_popdown(self.selection, self.entry) if not self.field: self.entry.set_active(-1) # When setting no item GTK doesn't clear the entry self.entry.get_child().set_text('') return super(Selection, self).display() value = self.field.get(self.record) if isinstance(value, (list, tuple)): # Compatibility with Many2One value = value[0] self.entry.handler_block_by_func(self.changed) if not self.set_popdown_value(self.entry, value): text = self.get_inactive_selection(value) self.set_popdown(self.selection[:] + [(value, text)], self.entry) self.set_popdown_value(self.entry, value) self.entry.handler_unblock_by_func(self.changed) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1707150267.0 tryton-7.0.24/tryton/gui/window/view_form/view/form_gtk/state_widget.py0000644000175000017500000001641414560205673024537 0ustar00cedced# This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. import functools from gi.repository import Gtk import tryton.common as common from tryton.action import Action from tryton.config import CONFIG from tryton.pyson import PYSONDecoder class StateMixin(object): def __init__(self, *args, **kwargs): self.attrs = kwargs.pop('attrs') super(StateMixin, self).__init__(*args, **kwargs) def state_set(self, record): if record: state_changes = record.expr_eval(self.attrs.get('states', {})) else: state_changes = {} if state_changes.get('invisible', self.attrs.get('invisible')): self.hide() else: self.show() class Label(StateMixin, Gtk.Label): def state_set(self, record): super(Label, self).state_set(record) if 'name' in self.attrs and record: field = record.group.fields[self.attrs['name']] else: field = None if not self.attrs.get('string', True): if field and record: text = field.get_client(record) or '' else: text = '' self.set_text(text) if record: state_changes = record.expr_eval(self.attrs.get('states', {})) else: state_changes = {} required = ((field and field.attrs.get('required')) or state_changes.get('required')) readonly = ((field and field.attrs.get('readonly')) or state_changes.get('readonly', not bool(field))) common.apply_label_attributes(self, readonly, required) class VBox(StateMixin, Gtk.VBox): pass class Image(StateMixin, Gtk.Image): def state_set(self, record): super(Image, self).state_set(record) if not record: return name = self.attrs['name'] if name in record.group.fields: field = record.group.fields[name] name = field.get(record) size = int(self.attrs.get('size', 48)) if self.attrs.get('type') == 'url': pixbuf = common.IconFactory.get_pixbuf_url( name, size=size, size_param=self.attrs.get('url_size')) else: pixbuf = common.IconFactory.get_pixbuf(name, size) self.set_from_pixbuf(pixbuf) class Frame(StateMixin, Gtk.Frame): def __init__(self, label=None, attrs=None): if not label: # label must be None to have no label widget label = None super(Frame, self).__init__(label=label, attrs=attrs) if not label: self.set_shadow_type(Gtk.ShadowType.NONE) self.set_border_width(0) class ScrolledWindow(StateMixin, Gtk.ScrolledWindow): def state_set(self, record): # Force to show first to ensure it is displayed in the Notebook self.show() super(ScrolledWindow, self).state_set(record) class Notebook(StateMixin, Gtk.Notebook): pass class Expander(StateMixin, Gtk.Expander): def __init__(self, label=None, attrs=None): if not label: label = None super(Expander, self).__init__(label=label, attrs=attrs) class Link(StateMixin, Gtk.Button): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.set_relief(Gtk.ReliefStyle.NONE) self.set_can_focus(False) if self.attrs.get('icon'): self.set_always_show_image(True) self.set_image(common.IconFactory.get_image( self.attrs['icon'], Gtk.IconSize.LARGE_TOOLBAR)) self.set_image_position(Gtk.PositionType.TOP) self._current = None @property def action_id(self): return int(self.attrs['id']) def state_set(self, record): super().state_set(record) if not self.get_visible(): return if CONFIG['client.modepda']: self.hide() return if record: if record.id < 0: self.hide() return data = { 'model': record.model_name, 'id': record.id, 'ids': [record.id], } context = record.get_context() pyson_ctx = { 'active_model': record.model_name, 'active_id': record.id, 'active_ids': [record.id], } self._current = record.id else: data = {} context = {} pyson_ctx = {} self._current = None pyson_ctx['context'] = context try: self.disconnect_by_func(self.__class__.clicked) except TypeError: pass self.connect('clicked', self.__class__.clicked, [data, context]) action = common.RPCExecute( 'model', 'ir.action', 'get_action_value', self.action_id, context=context) self.set_label(action['name']) decoder = PYSONDecoder(pyson_ctx) domain = decoder.decode(action['pyson_domain']) if action.get('pyson_search_value'): domain = [domain, decoder.decode(action['pyson_search_value'])] tab_domains = [(n, decoder.decode(d)) for n, d, c in action['domains'] if c] if tab_domains: label = ('%s\n' % action['name']) + '\n'.join( '%s (%%s)' % n for n, _ in tab_domains) else: label = '%s (%%s)' % action['name'] if record and self.action_id in record.links_counts: counter = record.links_counts[self.action_id] self._set_label_counter(label, counter) else: counter = [0] * (len(tab_domains) or 1) if record: record.links_counts[self.action_id] = counter if tab_domains: for i, (_, tab_domain) in enumerate(tab_domains): common.RPCExecute( 'model', action['res_model'], 'search_count', ['AND', domain, tab_domain], 0, 100, context=context, callback=functools.partial( self._set_count, idx=i, current=self._current, counter=counter, label=label)) else: common.RPCExecute( 'model', action['res_model'], 'search_count', domain, 0, 100, context=context, callback=functools.partial( self._set_count, current=self._current, counter=counter, label=label)) def _set_count(self, value, idx=0, current=None, counter=None, label=''): if current != self._current or not self.get_parent(): return try: count = value() if count > 99: count = '99+' counter[idx] = count except common.RPCException: pass self._set_label_counter(label, counter) def _set_label_counter(self, label, counter): self.set_label(label % tuple(counter)) if self.attrs.get('empty') == 'hide': if any(counter): self.show() else: self.hide() def clicked(self, data): Action.execute(self.action_id, *data, keyword=True) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1704711243.0 tryton-7.0.24/tryton/gui/window/view_form/view/form_gtk/textbox.py0000644000175000017500000001310714546752113023545 0ustar00cedced# This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. import logging from gi.repository import Gdk, Gtk try: from gi.repository import GtkSpell except ImportError: GtkSpell = None from tryton.config import CONFIG from .widget import TranslateMixin, Widget logger = logging.getLogger(__name__) class TextBox(Widget, TranslateMixin): expand = True def __init__(self, view, attrs): super(TextBox, self).__init__(view, attrs) self.widget = Gtk.VBox() self.scrolledwindow = Gtk.ScrolledWindow() self.scrolledwindow.set_policy( Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC) self.scrolledwindow.set_shadow_type(Gtk.ShadowType.ETCHED_IN) self.scrolledwindow.set_size_request(100, 100) self.textview = self.mnemonic_widget = self._get_textview() self.textview.connect('focus-out-event', lambda x, y: self._focus_out()) self.textview.connect('key-press-event', self.send_modified) # The click is grabbed by ListBox widget in this case user can never # set the input with a click self.textview.add_events(Gdk.EventMask.BUTTON_PRESS_MASK) self.textview.connect_after('button-press-event', self._button_press) self.scrolledwindow.add(self.textview) self.scrolledwindow.show_all() self.button = None if attrs.get('translate'): self.button = self.translate_button() self.widget.pack_end( self.button, expand=False, fill=False, padding=0) self.widget.pack_end( self.scrolledwindow, expand=True, fill=True, padding=0) def _get_textview(self): if self.attrs.get('size'): textbuffer = TextBufferLimitSize(int(self.attrs['size'])) textview = Gtk.TextView() textview.set_buffer(textbuffer) else: textview = Gtk.TextView() textview.set_wrap_mode(Gtk.WrapMode.WORD) # TODO better tab solution textview.set_accepts_tab(False) return textview def _button_press(self, textview, event): textview.grab_focus() return True def translate_widget(self): box = Gtk.VBox() scrolledwindow = Gtk.ScrolledWindow() scrolledwindow.set_policy( Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC) scrolledwindow.set_shadow_type(Gtk.ShadowType.ETCHED_IN) scrolledwindow.set_size_request(-1, 80) textview = self._get_textview() scrolledwindow.add(textview) box.pack_end(scrolledwindow, expand=True, fill=True, padding=0) return box def translate_widget_set(self, widget, value): textview = widget.get_children()[-1].get_child() self.set_buffer(value, textview) def translate_widget_get(self, widget): textview = widget.get_children()[-1].get_child() return self.get_buffer(textview) def translate_widget_set_readonly(self, widget, value): textview = widget.get_children()[-1].get_child() textview.set_editable(not value) textview.props.sensitive = not value def _readonly_set(self, value): super(TextBox, self)._readonly_set(value) self.textview.set_editable(not value) @property def modified(self): if self.record and self.field: return self.field.get_client(self.record) != self.get_value() return False def get_value(self): return self.get_buffer(self.textview) def set_value(self): self.field.set_client(self.record, self.get_value()) def set_buffer(self, value, textview): buf = textview.get_buffer() buf.delete(buf.get_start_iter(), buf.get_end_iter()) iter_start = buf.get_start_iter() buf.insert(iter_start, value) def get_buffer(self, textview): buf = textview.get_buffer() iter_start = buf.get_start_iter() iter_end = buf.get_end_iter() return buf.get_text(iter_start, iter_end, False) def display(self): super(TextBox, self).display() value = self.field and self.field.get(self.record) if not value: value = '' self.set_buffer(value, self.textview) if (GtkSpell and self.textview.get_editable() and self.attrs.get('spell') and CONFIG['client.spellcheck']): checker = GtkSpell.Checker.get_from_text_view(self.textview) if self.record: language = self.record.expr_eval(self.attrs['spell']) if not checker: checker = GtkSpell.Checker() checker.attach(self.textview) if checker.get_language() != language: try: checker.set_language(language) except Exception: logger.debug( 'Could not set spell checker to "%s"', language) checker.detach() elif checker: checker.detach() class TextBufferLimitSize(Gtk.TextBuffer): __gsignals__ = { 'insert-text': 'override', } def __init__(self, max_length): super(TextBufferLimitSize, self).__init__() self.max_length = max_length def do_insert_text(self, iter, text, length): free_chars = self.max_length - self.get_char_count() text = text[0:free_chars] length = len(text.encode('utf-8')) return Gtk.TextBuffer.do_insert_text(self, iter, text, length) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685599.0 tryton-7.0.24/tryton/gui/window/view_form/view/form_gtk/timedelta.py0000644000175000017500000000312314517761237024022 0ustar00cedced# This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. from gi.repository import Gtk from tryton.common.entry_position import reset_position from .widget import Widget class TimeDelta(Widget): def __init__(self, view, attrs): super(TimeDelta, self).__init__(view, attrs) self.widget = Gtk.HBox() self.entry = self.mnemonic_widget = Gtk.Entry() self.entry.set_alignment(1.0) self.entry.set_property('activates_default', True) self.entry.connect('activate', self.sig_activate) self.entry.connect('focus-out-event', lambda x, y: self._focus_out()) self.entry.connect('key-press-event', self.send_modified) self.widget.pack_start(self.entry, expand=True, fill=True, padding=0) @property def modified(self): if self.record and self.field: value = self.entry.get_text() return self.field.get_client(self.record) != value return False def set_value(self): value = self.entry.get_text() return self.field.set_client(self.record, value) def get_value(self): return self.entry.get_text() def display(self): super(TimeDelta, self).display() if not self.field: value = '' else: value = self.field.get_client(self.record) self.entry.set_text(value) reset_position(self.entry) def _readonly_set(self, value): super(TimeDelta, self)._readonly_set(value) self.entry.set_editable(not value) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685599.0 tryton-7.0.24/tryton/gui/window/view_form/view/form_gtk/url.py0000644000175000017500000001175614517761237022667 0ustar00cedced# This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. import gettext import webbrowser from urllib.parse import urlencode, urljoin from gi.repository import Gtk import tryton.common as common from tryton.common.common import selection from tryton.common.underline import set_underline from tryton.config import CONFIG from tryton.rpc import CONNECTION from .char import Char from .widget import TranslateMixin, Widget _ = gettext.gettext class URL(Char): "url" def __init__(self, view, attrs): super(URL, self).__init__(view, attrs) self.tooltips = common.Tooltips() self.button = Gtk.Button() self.button.set_image(common.IconFactory.get_image( 'tryton-public', Gtk.IconSize.SMALL_TOOLBAR)) self.button.set_relief(Gtk.ReliefStyle.NONE) self.button.connect('clicked', self.button_clicked) self.widget.pack_start( self.button, expand=False, fill=False, padding=0) def display(self): super(URL, self).display() self.set_tooltips() if self.record and 'icon' in self.attrs: icon = self.attrs['icon'] if icon in self.record.group.fields: value = self.record[icon].get_client(self.record) value = value if value else 'tryton-public' else: value = icon self.button.set_image(common.IconFactory.get_image( value, Gtk.IconSize.SMALL_TOOLBAR)) self.button.set_sensitive(bool(self.entry.get_text())) def set_tooltips(self): value = self.entry.get_text() if value: self.tooltips.enable() self.tooltips.set_tip(self.button, value) else: self.tooltips.set_tip(self.button, '') self.tooltips.disable() def button_clicked(self, widget): value = self.entry.get_text() if value: webbrowser.open(value, new=2) class Email(URL): "email" def button_clicked(self, widget): value = self.entry.get_text() if value: webbrowser.open('mailto:%s' % value, new=2) def set_tooltips(self): value = self.entry.get_text() if value: self.tooltips.enable() self.tooltips.set_tip(self.button, 'mailto:%s' % value) else: self.tooltips.set_tip(self.button, '') self.tooltips.disable() class CallTo(URL): "call to" def button_clicked(self, widget): value = self.entry.get_text() if value: webbrowser.open('callto:%s' % value, new=2) def set_tooltips(self): value = self.entry.get_text() if value: self.tooltips.enable() self.tooltips.set_tip(self.button, 'callto:%s' % value) else: self.tooltips.set_tip(self.button, '') self.tooltips.disable() class SIP(URL): "sip" def button_clicked(self, widget): value = self.entry.get_text() if value: webbrowser.open('sip:%s' % value, new=2) def set_tooltips(self): value = self.entry.get_text() if value: self.tooltips.enable() self.tooltips.set_tip(self.button, 'sip:%s' % value) else: self.tooltips.set_tip(self.button, '') self.tooltips.disable() class HTML(Widget, TranslateMixin): "HTML" def __init__(self, view, attrs): super().__init__(view, attrs) self.widget = Gtk.HBox() self.mnemonic_widget = self.button = Gtk.LinkButton() self.button.set_label(set_underline(attrs['string'])) self.button.set_use_underline(True) self.button.set_alignment(0, 0.5) self.widget.pack_start( self.button, expand=False, fill=False, padding=0) if attrs.get('translate'): self.widget.pack_start( self.translate_button(), expand=False, fill=False, padding=0) def uri(self, language=None): if not self.record or self.record.id < 0 or CONNECTION.url is None: uri = '' else: path = ['ir/html', self.model_name, str(self.record.id), self.field_name] params = { 'language': language or CONFIG['client.lang'], 'title': CONFIG['client.title'], } uri = urljoin( CONNECTION.url + '/', '/'.join(path) + '?' + urlencode(params)) return uri def display(self): super().display() self.button.set_uri(self.uri()) def _readonly_set(self, value): super()._readonly_set(value) for button in self.widget.get_children(): button.set_sensitive(not value) def translate_dialog(self, languages): languages = {l['name']: l['code'] for l in languages} result = selection(_('Choose a language'), languages) if result: webbrowser.open(self.uri(language=result[1]), new=2) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685599.0 tryton-7.0.24/tryton/gui/window/view_form/view/form_gtk/widget.py0000644000175000017500000002467214517761237023351 0ustar00cedced# This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. import gettext from gi.repository import Gdk, GLib, Gtk import tryton.common as common from tryton.common import TRYTON_ICON, RPCException, RPCExecute from tryton.common.underline import set_underline from tryton.common.widget_style import widget_class from tryton.gui import Main from tryton.gui.window.nomodal import NoModal _ = gettext.gettext class Widget(object): expand = False default_width_chars = 8 def __init__(self, view, attrs): super(Widget, self).__init__() self.view = view self.attrs = attrs self.widget = None self.mnemonic_widget = None self.visible = True self._readonly = False @property def field_name(self): return self.attrs['name'] @property def model_name(self): return self.view.screen.model_name @property def record(self): return self.view.record @property def field(self): if self.record: return self.record.group.fields[self.field_name] return None def destroy(self): pass def sig_activate(self, widget=None): # emulate a focus_out so that the onchange is called if needed self._focus_out() def _readonly_set(self, readonly): self._readonly = readonly def _required_set(self, required): pass def _invisible_widget(self): return self.widget @property def _invalid_widget(self): return self.widget @property def _required_widget(self): return self.widget @property def modified(self): return False def send_modified(self, *args): def send(value): if not self.widget.props.window: return if self.record and self.get_value() == value and self.modified: self.view.screen.record_modified(display=False) def get_value(): if not self.widget.props.window: return GLib.timeout_add(300, send, self.get_value()) # Wait the current event is finished to retreive the value GLib.idle_add(get_value) return False def invisible_set(self, value): widget = self._invisible_widget() if value and value != '0': self.visible = False widget.hide() else: self.visible = True widget.show() def _focus_out(self, *args): if not self.field: return False if not self.visible: return False self.set_value() def display(self): if not self.field: self._readonly_set(self.attrs.get('readonly', True)) self.invisible_set(self.attrs.get('invisible', False)) self._required_set(False) return states = self.field.get_state_attrs(self.record) readonly = self.attrs.get('readonly', states.get('readonly', False)) if self.view.screen.readonly: readonly = True self._readonly_set(readonly) widget_class(self.widget, 'readonly', readonly) self._required_set(not readonly and states.get('required', False)) widget_class( self._required_widget, 'required', not readonly and states.get('required', False)) invalid = states.get('invalid', False) widget_class(self._invalid_widget, 'invalid', not readonly and invalid) self.invisible_set(self.attrs.get( 'invisible', states.get('invisible', False))) def get_value(self): pass def set_value(self): pass class TranslateDialog(NoModal): def __init__(self, widget, languages, readonly): NoModal.__init__(self) self.widget = widget self.win = Gtk.Dialog( title=_('Translation'), transient_for=self.parent, destroy_with_parent=True) Main().add_window(self.win) self.win.set_position(Gtk.WindowPosition.CENTER_ON_PARENT) self.win.set_icon(TRYTON_ICON) self.win.connect('response', self.response) self.win.set_default_size(*self.default_size()) self.accel_group = Gtk.AccelGroup() self.win.add_accel_group(self.accel_group) cancel_button = self.win.add_button( set_underline(_("Cancel")), Gtk.ResponseType.CANCEL) cancel_button.set_image( common.IconFactory.get_image('tryton-cancel', Gtk.IconSize.BUTTON)) cancel_button.set_always_show_image(True) ok_button = self.win.add_button( set_underline(_("OK")), Gtk.ResponseType.OK) ok_button.set_image( common.IconFactory.get_image('tryton-ok', Gtk.IconSize.BUTTON)) ok_button.set_always_show_image(True) ok_button.add_accelerator( 'clicked', self.accel_group, Gdk.KEY_Return, Gdk.ModifierType.CONTROL_MASK, Gtk.AccelFlags.VISIBLE) tooltips = common.Tooltips() self.widgets = {} grid = Gtk.Grid(column_spacing=3, row_spacing=3) for i, language in enumerate(languages): label = language['name'] + _(':') label = Gtk.Label( label=label, halign=Gtk.Align.END, valign=(Gtk.Align.START if self.widget.expand else Gtk.Align.FILL)) grid.attach(label, 0, i, 1, 1) context = dict( language=language['code'], fuzzy_translation=False, ) try: value = RPCExecute('model', self.widget.record.model_name, 'read', [self.widget.record.id], [self.widget.field_name], context={'language': language['code']} )[0][self.widget.field_name] except RPCException: return context['fuzzy_translation'] = True try: fuzzy_value = RPCExecute('model', self.widget.record.model_name, 'read', [self.widget.record.id], [self.widget.field_name], context=context)[0][self.widget.field_name] except RPCException: return if fuzzy_value is None: fuzzy_value = '' widget = self.widget.translate_widget() label.set_mnemonic_widget(widget) self.widget.translate_widget_set(widget, fuzzy_value) self.widget.translate_widget_set_readonly(widget, True) widget.set_vexpand(self.widget.expand) widget.set_hexpand(True) grid.attach(widget, 1, i, 1, 1) editing = Gtk.ToggleButton(label=_("Edit")) editing.connect('toggled', self.editing_toggled, widget) editing.props.sensitive = not readonly grid.attach(editing, 2, i, 1, 1) if value != fuzzy_value: fuzzy = Gtk.Label(_("Fuzzy")) widget_class(fuzzy, 'warning', True) grid.attach(fuzzy, 4, i, 1, 1) self.widgets[language['code']] = (widget, editing) tooltips.enable() vbox = Gtk.VBox() vbox.pack_start(grid, expand=self.widget.expand, fill=True, padding=0) viewport = Gtk.Viewport() viewport.set_shadow_type(Gtk.ShadowType.NONE) viewport.add(vbox) scrolledwindow = Gtk.ScrolledWindow() scrolledwindow.set_policy( Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC) scrolledwindow.set_shadow_type(Gtk.ShadowType.NONE) scrolledwindow.add(viewport) self.win.vbox.pack_start( scrolledwindow, expand=True, fill=True, padding=0) self.win.show_all() self.register() self.show() def editing_toggled(self, editing, widget): self.widget.translate_widget_set_readonly(widget, not editing.get_active()) def response(self, win, response): if response == Gtk.ResponseType.OK: for code, widget in self.widgets.items(): widget, editing = widget if not editing.get_active(): continue value = self.widget.translate_widget_get(widget) context = dict( language=code, fuzzy_translation=False, ) try: RPCExecute('model', self.widget.record.model_name, 'write', [self.widget.record.id], { self.widget.field_name: value, }, context=context) except RPCException: pass self.widget.record.cancel() self.widget.view.display() self.destroy() def destroy(self): self.win.destroy() NoModal.destroy(self) def show(self): self.win.show() def hide(self): self.win.hide() class TranslateMixin: def translate_button(self): button = Gtk.Button() button.set_image(common.IconFactory.get_image( 'tryton-translate', Gtk.IconSize.SMALL_TOOLBAR)) button.set_relief(Gtk.ReliefStyle.NONE) button.connect('clicked', self.translate) return button def translate(self, widget=None, *args): if widget: widget.grab_focus() self.view.set_value() if self.record.id < 0 or self.record.modified: common.message( _('You need to save the record before adding translations.')) return try: lang_ids = RPCExecute('model', 'ir.lang', 'search', [ ('translatable', '=', True), ]) except RPCException: return if not lang_ids: common.message(_('No other language available.')) return try: languages = RPCExecute('model', 'ir.lang', 'read', lang_ids, ['code', 'name']) except RPCException: return self.translate_dialog(languages) def translate_dialog(self, languages): TranslateDialog(self, languages, self._readonly) def translate_widget(self): raise NotImplementedError def translate_widget_set(self, widget, value): raise NotImplementedError def translate_widget_get(self, widget): raise NotImplementedError def translate_widget_set_readonly(self, widget, value): raise NotImplementedError ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685599.0 tryton-7.0.24/tryton/gui/window/view_form/view/graph.py0000644000175000017500000001454314517761237021353 0ustar00cedced# This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. import gettext import sys from gi.repository import Gtk from tryton.common import ( IconFactory, file_selection, get_toplevel_window, message, node_attributes) from tryton.common.underline import set_underline from tryton.config import TRYTON_ICON from tryton.gui import Main from . import View, XMLViewParser from .graph_gtk.bar import HorizontalBar, VerticalBar from .graph_gtk.line import Line from .graph_gtk.pie import Pie _ = gettext.gettext class GraphXMLViewParser(XMLViewParser): WIDGETS = { 'hbar': HorizontalBar, 'line': Line, 'pie': Pie, 'vbar': VerticalBar, } def __init__(self, view, exclude_field, field_attrs): super().__init__(view, exclude_field, field_attrs) self._xfield = None self._yfields = [] def _node_attributes(self, node): node_attrs = node_attributes(node) if 'name' in node_attrs: if not node_attrs.get('string') and node_attrs['name'] != '#': field = self.field_attrs[node_attrs['name']] node_attrs['string'] = field['string'] return node_attrs def _parse_graph(self, node, attributes): for child in node.childNodes: self.parse(child) Widget = self.WIDGETS.get(attributes.get('type', 'vbar')) widget = Widget( self.view, self._xfield, self._yfields) self.view.widget.add(widget) self.view.widgets['root'] = widget def _parse_x(self, node, attributes): for child in node.childNodes: if not child.nodeType == child.ELEMENT_NODE: continue self._xfield = self._node_attributes(child) def _parse_y(self, node, attributes): for child in node.childNodes: if not child.nodeType == child.ELEMENT_NODE: continue self._yfields.append(self._node_attributes(child)) class ViewGraph(View): view_type = 'graph' editable = False creatable = False xml_parser = GraphXMLViewParser def __init__(self, view_id, screen, xml): self.widget = event = Gtk.EventBox() super().__init__(view_id, screen, xml) event.connect('button-press-event', self.button_press) def __getitem__(self, name): return None def destroy(self): self.widget.destroy() self.widgets['root'].destroy() self.widgets.clear() def set_value(self): pass def reset(self): pass def display(self): self.widgets['root'].display(self.screen.group) return True def set_cursor(self, new=False, reset_view=True): pass def get_fields(self): return [] def get_buttons(self): return [] def save(self, widget): parent = get_toplevel_window() dia = Gtk.Dialog( title=_('Image Size'), transient_for=parent, modal=True, destroy_with_parent=True) Main().add_window(dia) cancel_button = dia.add_button( set_underline(_("Cancel")), Gtk.ResponseType.CANCEL) cancel_button.set_image(IconFactory.get_image( 'tryton-cancel', Gtk.IconSize.BUTTON)) cancel_button.set_always_show_image(True) ok_button = dia.add_button( set_underline(_("OK")), Gtk.ResponseType.OK) ok_button.set_image(IconFactory.get_image( 'tryton-ok', Gtk.IconSize.BUTTON)) ok_button.set_always_show_image(True) dia.set_icon(TRYTON_ICON) dia.set_default_response(Gtk.ResponseType.OK) hbox = Gtk.HBox(spacing=3) dia.vbox.pack_start(hbox, expand=False, fill=True, padding=0) hbox.pack_start( Gtk.Label(label=_('Width:')), expand=False, fill=True, padding=0) spinwidth = Gtk.SpinButton() spinwidth.configure(Gtk.Adjustment( value=400.0, lower=0.0, upper=sys.maxsize, step_increment=1.0, page_increment=10.0), climb_rate=1, digits=0) spinwidth.set_numeric(True) spinwidth.set_activates_default(True) hbox.pack_start(spinwidth, expand=True, fill=True, padding=0) hbox.pack_start( Gtk.Label(label=_('Height:')), expand=False, fill=True, padding=0) spinheight = Gtk.SpinButton() spinheight.configure(Gtk.Adjustment( value=200.0, lower=0.0, upper=sys.maxsize, step_increment=1.0, page_increment=10.0), climb_rate=1, digits=0) spinheight.set_numeric(True) spinheight.set_activates_default(True) hbox.pack_start(spinheight, expand=True, fill=True, padding=0) dia.show_all() filter = Gtk.FileFilter() filter.set_name(_('PNG image (*.png)')) filter.add_mime_type('image/png') filter.add_pattern('*.png') while True: response = dia.run() width = spinwidth.get_value_as_int() height = spinheight.get_value_as_int() if response == Gtk.ResponseType.OK: filename = file_selection( _('Save As'), action=Gtk.FileChooserAction.SAVE, preview=False, filters=[filter]) if width and height and filename: filename = filename.with_suffix('.png') try: self.widgets['root'].export_png( filename, width, height) break except MemoryError: message( _('Image size too large.'), dia, Gtk.MessageType.ERROR) else: break parent.present() dia.destroy() def button_press(self, widget, event): if event.button == 3: menu = Gtk.Menu() item = Gtk.MenuItem(label=_('Save As...')) item.connect('activate', self.save) item.show() menu.append(item) if hasattr(menu, 'popup_at_pointer'): menu.popup_at_pointer(event) else: menu.popup(None, None, None, event.button, event.time) return True elif event.button == 1: self.widgets['root'].action() ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1745680285.227445 tryton-7.0.24/tryton/gui/window/view_form/view/graph_gtk/0000755000175000017500000000000015003173635021625 5ustar00cedced././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685599.0 tryton-7.0.24/tryton/gui/window/view_form/view/graph_gtk/__init__.py0000644000175000017500000000022014517761237023742 0ustar00cedced# This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685599.0 tryton-7.0.24/tryton/gui/window/view_form/view/graph_gtk/bar.py0000644000175000017500000002106014517761237022754 0ustar00cedced# This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. # This code is inspired by the pycha project # (http://www.lorenzogil.com/projects/pycha/) import datetime import locale import math import cairo import tryton.common as common import tryton.rpc as rpc from .graph import Graph class Bar(Graph): def __init__(self, *args, **kwargs): super(Bar, self).__init__(*args, **kwargs) self.bars = [] def drawGraph(self, cr, width, height): def drawBar(bar): cr.set_line_width(0.5) x = self.area.w * bar.x + self.area.x y = self.area.h * bar.y + self.area.y w = self.area.w * bar.w h = self.area.h * bar.h if w < 1 or h < 1: return # don't draw too small self.drawRectangle(cr, x, y, w, h) r, g, b = self.colorScheme[bar.yname] if bar.highlight: r, g, b = common.highlight_rgb(r, g, b) cr.set_source(self.sourceRectangle(x, y, w, h, r, g, b)) cr.fill_preserve() cr.stroke() cr.save() for bar in self.bars: drawBar(bar) cr.restore() def drawRectangle(self, cr, x, y, w, h): radius = 2.5 cr.arc(x + radius, y + radius, radius, 0, 2 * math.pi) cr.arc(x + w - radius, y + radius, radius, 0, 2 * math.pi) cr.arc(x + w - radius, y + h - radius, radius, 0, 2 * math.pi) cr.arc(x + radius, y + h - radius, radius, 0, 2 * math.pi) cr.rectangle(x + radius, y, w - radius * 2, h) cr.rectangle(x, y + radius, w, h - radius * 2) def sourceRectangle(self, x, y, w, h, r, g, b): linear = cairo.LinearGradient((x + w) / 2, y, (x + w) / 2, y + h) linear.add_color_stop_rgb(0, 3.5 * r / 5.0, 3.5 * g / 5.0, 3.5 * b / 5.0) linear.add_color_stop_rgb(1, r, g, b) return linear def motion(self, widget, event): super(Bar, self).motion(widget, event) if not getattr(self, 'area', None): return def intersect(bar, event): x = self.area.w * bar.x + self.area.x y = self.area.h * bar.y + self.area.y w = self.area.w * bar.w h = self.area.h * bar.h if x <= event.x <= x + w and y <= event.y <= y + h: return True return False highlight = False draw_bars = [] yfields_timedelta = {x.get('key', x['name']): x.get('timedelta') for x in self.yfields if 'timedelta' in x} for bar in self.bars: if intersect(bar, event): if not bar.highlight: bar.highlight = True if bar.yname in yfields_timedelta: converter = None if yfields_timedelta[bar.yname]: converter = rpc.CONTEXT.get( yfields_timedelta[bar.yname]) label = common.timedelta.format( datetime.timedelta(seconds=bar.yval), converter) else: label = locale.localize( '{:.2f}'.format(bar.yval), True) label += '\n' label += str(self.labels[bar.xname]) self.popup.set_text(label) draw_bars.append(bar) else: if bar.highlight: bar.highlight = False draw_bars.append(bar) if bar.highlight: highlight = True if highlight: self.popup.show() else: self.popup.hide() if draw_bars: minx = self.area.w + self.area.x miny = self.area.h + self.area.y maxx = maxy = 0.0 for bar in draw_bars: x = self.area.w * bar.x + self.area.x y = self.area.h * bar.y + self.area.y minx = int(min(x, minx)) miny = int(min(y, miny)) maxx = int(max(x + self.area.w * bar.w, maxx)) maxy = int(max(y + self.area.h * bar.h, maxy)) self.queue_draw_area(minx - 1, miny - 1, maxx - minx + 2, maxy - miny + 2) def action(self): super(Bar, self).action() for bar in self.bars: if bar.highlight: ids = self.ids[bar.xname] self.action_keyword(ids) class VerticalBar(Bar): 'Vertical Bar Graph' def updateGraph(self): barWidth = self.xscale * 0.9 barMargin = self.xscale * (1.0 - 0.9) / 2 self.bars = [] i = 0 keys = list(self.datas.keys()) keys.sort() for xfield in keys: j = 0 barWidthForSet = barWidth / len(self.datas[xfield]) for yfield in self._getDatasKeys(): xval = i yval = self.datas[xfield][yfield] x = (xval - self.minxval) * self.xscale + \ barMargin + (j * barWidthForSet) y = 1.0 - (yval - self.minyval) * self.yscale w = barWidthForSet h = yval * self.yscale if h < 0: h = abs(h) y -= h rect = Rect(x, y, w, h, xval, yval, xfield, yfield) if (0.0 <= rect.x <= 1.0) and (0.0 <= rect.y <= 1.0): self.bars.append(rect) j += 1 i += 1 def XLabels(self): xlabels = super(VerticalBar, self).XLabels() return [(x[0] + (self.xscale / 2), x[1]) for x in xlabels] def YLabels(self): ylabels = super(VerticalBar, self).YLabels() if all('timedelta' in f for f in self.yfields): converter = {f.get('timedelta') for f in self.yfields} if len(converter) == 1: converter = rpc.CONTEXT.get(converter.pop()) else: converter = None return [ (x[0], common.timedelta.format( datetime.timedelta(seconds=locale.atof(x[1])), converter)) for x in ylabels] return ylabels class HorizontalBar(Bar): 'Horizontal Bar Graph' def updateGraph(self): barWidth = self.xscale * 0.9 barMargin = self.xscale * (1.0 - 0.9) / 2 self.bars = [] i = 0 keys = list(self.datas.keys()) keys.sort() for xfield in keys: j = 0 barWidthForSet = barWidth / len(self.datas[xfield]) for yfield in self._getDatasKeys(): xval = i yval = self.datas[xfield][yfield] x = - self.minyval * self.yscale y = (xval - self.minxval) * self.xscale + \ barMargin + (j * barWidthForSet) w = yval * self.yscale h = barWidthForSet if w < 0: w = abs(w) x -= w rect = Rect(x, y, w, h, xval, yval, xfield, yfield) if (0.0 <= rect.x <= 1.0) and (0.0 <= rect.y <= 1.0): self.bars.append(rect) j += 1 i += 1 def YLabels(self): xlabels = super(HorizontalBar, self).XLabels() return [(1 - (x[0] + (self.xscale / 2)), x[1]) for x in xlabels] def XLabels(self): ylabels = super(HorizontalBar, self).YLabels() if all('timedelta' in f for f in self.yfields): converter = {f.get('timedelta') for f in self.yfields} if len(converter) == 1: converter = rpc.CONTEXT.get(converter.pop()) else: converter = None return [ (x[0], common.timedelta.format( datetime.timedelta(seconds=locale.atof(x[1])), converter)) for x in ylabels] return [(x[0], x[1]) for x in ylabels] def _getLegendPosition(self, width, height): return self.area.x + self.area.w * 0.95 - width, \ self.area.y + self.area.h * 0.05 def drawLines(self, cr, width, height): for w, label in self.XLabels(): self.drawLine(cr, w, 0) class Rect(object): def __init__(self, x, y, w, h, xval, yval, xname, yname): self.x, self.y, self.w, self.h = x, y, w, h self.xval, self.yval = xval, yval self.yname = yname self.xname = xname self.highlight = False ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1725720314.0 tryton-7.0.24/tryton/gui/window/view_form/view/graph_gtk/graph.py0000644000175000017500000003715014667063372023321 0ustar00cedced# This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. # This code is inspired by the pycha project # (http://www.lorenzogil.com/projects/pycha/) import datetime import locale import math from functools import reduce import cairo from dateutil.relativedelta import relativedelta from gi.repository import Gdk, Gtk import tryton.rpc as rpc from tryton import common from tryton.action import Action from tryton.common import COLOR_SCHEMES, generateColorscheme, hex2rgb from tryton.config import CONFIG from tryton.gui.window import Window from tryton.pyson import PYSONDecoder class Popup(object): def __init__(self, widget): self.win = Gtk.Window(type=Gtk.WindowType.POPUP) self.win.set_name('gtk-tooltips') self.win.set_resizable(False) self.win.set_border_width(1) self.win.set_transient_for(widget.props.window) self.label = Gtk.Label() self.win.add(self.label) self.win.connect('enter-notify-event', self.enter) def set_text(self, text): self.label.set_text(text) def set_position(self, widget, x, y): origin = widget.props.window.get_origin() allocation = widget.get_allocation() width, height = allocation.width, allocation.height popup_width, popup_height = self.win.get_size() if x < popup_width // 2: x = popup_width // 2 if x > width - popup_width // 2: x = width - popup_width // 2 pos_x = origin.x + x - popup_width // 2 if pos_x < 0: pos_x = 0 if y < popup_height + 5: y = popup_height + 5 if y > height: y = height pos_y = origin.y + y - popup_height - 5 if pos_y < 0: pos_y = 0 self.win.move(int(pos_x), int(pos_y)) def show(self): self.win.show_all() def hide(self): self.win.hide() def destroy(self): self.win.destroy() def enter(self, widget, event): self.win.hide() class Graph(Gtk.DrawingArea): 'Graph' __gsignals__ = {"draw": "override"} def __init__(self, view, xfield, yfields): super(Graph, self).__init__() self.view = view self.xfield = xfield self.yfields = yfields self.datas = {} self.topPadding = 15 self.bottomPadding = 15 self.rightPadding = 30 self.leftPadding = 30 self.set_events(Gdk.EventMask.POINTER_MOTION_MASK) self.connect('motion-notify-event', self.motion) self.connect('leave-notify-event', self.leave) self.popup = Popup(self) @property def attrs(self): return self.view.attributes @property def model(self): return self.view.screen.model_name def destroy(self): self.popup.destroy() super(Graph, self).destroy() def motion(self, widget, event): self.popup.set_position(self, event.x, event.y) def leave(self, widget, event): self.popup.hide() def do_draw(self, cr): width = self.get_allocated_width() height = self.get_allocated_height() self.updateArea(cr, width, height) self.drawBackground(cr, width, height) self.drawLines(cr, width, height) self.drawGraph(cr, width, height) self.drawAxis(cr, width, height) self.drawLegend(cr, width, height) def export_png(self, filename, width, height): surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, width, height) cx = cairo.Context(surface) self.updateArea(cx, width, height) self.drawBackground(cx, width, height) self.drawLines(cx, width, height) self.drawGraph(cx, width, height) self.drawAxis(cx, width, height) self.drawLegend(cx, width, height) surface.write_to_png(filename) self.queue_draw() def action(self): self.popup.hide() def action_keyword(self, ids): if not ids: return ctx = self.group._context.copy() if 'active_ids' in ctx: del ctx['active_ids'] if 'active_id' in ctx: del ctx['active_id'] event = Gtk.get_current_event() allow_similar = False if (event.state & Gdk.ModifierType.CONTROL_MASK or event.state & Gdk.ModifierType.MOD1_MASK): allow_similar = True with Window(hide_current=True, allow_similar=allow_similar): return Action.exec_keyword('graph_open', { 'model': self.model, 'id': ids[0], 'ids': ids, }, context=ctx, warning=False) def drawBackground(self, cr, width, height): # Fill the background cr.save() r, g, b = hex2rgb(self.attrs.get('background', '#d5d5d5')) linear = cairo.LinearGradient(width // 2, 0, width // 2, height) linear.add_color_stop_rgb(0, 1, 1, 1) linear.add_color_stop_rgb(1, r, g, b) cr.set_source(linear) cr.rectangle(0, 0, width, height) cr.fill() cr.stroke() cr.restore() def drawGraph(self, cr, width, height): pass def YLabels(self): ylabels = [] if self.yrange == 0.0: base = 1 else: base = 10 ** int(math.log(self.yrange, 10)) for i in range(int(self.yrange / base) + 1): val = int(self.minyval / base) * base + i * base h = (val - self.minyval) * self.yscale label = locale.localize('{:.2f}'.format(val), True) ylabels.append((h, label)) return ylabels def XLabels(self): xlabels = [] i = 0.0 keys = list(self.datas.keys()) keys.sort() for key in keys: if self.xrange == 0: w = 1.0 else: w = i / self.xrange xlabels.append((w, str(self.labels[key]))) i += 1 return xlabels def drawAxis(self, cr, width, height): cr.set_source_rgb(*hex2rgb('#000000')) cr.set_line_width(0.5) # Y axis def drawYLabel(h, label): x = self.area.x y = self.area.y + self.area.h - h * self.area.h cr.new_path() cr.move_to(x, y) cr.line_to(x - 3.0, y) cr.close_path() cr.stroke() extends = cr.text_extents(label) labelWidth = extends[2] labelHeight = extends[3] if labelWidth <= self.area.x: cr.move_to(x - 3.0 - labelWidth - 5, y + labelHeight / 2.0) cr.show_text(label) for h, label in self.YLabels(): drawYLabel(h, label) cr.new_path() cr.move_to(self.area.x, self.area.y) cr.line_to(self.area.x, self.area.y + self.area.h) cr.close_path() cr.stroke() # X axis def drawXLabel(w, label): x = self.area.x + w * self.area.w y = self.area.y + self.area.h cr.new_path() cr.move_to(x, y) cr.line_to(x, y + 3.0) cr.close_path() cr.stroke() extends = cr.text_extents(label) labelWidth = extends[2] labelHeight = extends[3] if labelWidth <= self.xscale * self.area.w: cr.move_to(x - labelWidth / 2.0, y + labelHeight + 5) cr.show_text(label) for w, label in self.XLabels(): drawXLabel(w, label) cr.new_path() cr.move_to(self.area.x, self.area.y + self.area.h) cr.line_to(self.area.x + self.area.w, self.area.y + self.area.h) cr.close_path() cr.stroke() def drawLines(self, cr, width, height): for h, label in self.YLabels(): self.drawLine(cr, 0, h) def drawLine(self, cr, x, y): if x: x1 = x2 = self.area.x + x * self.area.w y1 = self.area.y y2 = y1 + self.area.h else: y1 = y2 = self.area.y + self.area.h - y * self.area.h x1 = self.area.x x2 = x1 + self.area.w cr.save() cr.set_source_rgb(*hex2rgb('#A0A0A0')) cr.set_line_width(0.3) cr.new_path() cr.set_dash([5.0, 4.0]) cr.move_to(x1, y1) cr.line_to(x2, y2) cr.close_path() cr.stroke() cr.restore() def drawLegend(self, cr, width, height): if not int(self.attrs.get('legend', 1)): return padding = 4 bullet = 15 width = 0 height = padding keys = self._getDatasKeys() if not keys: return keys2txt = {} for yfield in self.yfields: keys2txt[yfield.get('key', yfield['name'])] = yfield['string'] for key in keys: extents = cr.text_extents(keys2txt[key]) width = max(extents[2], width) height += max(extents[3], bullet) + padding width = padding + bullet + padding + width + padding pos_x, pos_y = self._getLegendPosition(width, height) cr.save() cr.rectangle(pos_x, pos_y, width, height) cr.set_source_rgba(1, 1, 1, 0.8) cr.fill_preserve() cr.set_line_width(0.5) cr.set_source_rgb(*hex2rgb('#000000')) cr.stroke() def drawKey(key, x, y, text_height): cr.rectangle(x, y, bullet, bullet) cr.set_source_rgb(*self.colorScheme[key]) cr.fill_preserve() cr.set_source_rgb(0, 0, 0) cr.stroke() cr.move_to(x + bullet + padding, y + bullet / 2.0 + text_height / 2.0) cr.show_text(keys2txt[key]) cr.set_line_width(0.5) x = pos_x + padding y = pos_y + padding for key in keys: extents = cr.text_extents(keys2txt[key]) drawKey(key, x, y, extents[3]) y += max(extents[3], bullet) + padding cr.restore() def _getLegendPosition(self, width, height): return self.area.x + self.area.w * 0.05, \ self.area.y + self.area.h * 0.05 def display(self, group): self.updateDatas(group) self.setColorScheme() self.updateXY() self.updateGraph() self.queue_draw() def updateDatas(self, group): self.datas = {} self.labels = {} self.ids = {} self.group = group minx = None maxx = None for model in group: x = model[self.xfield['name']].get(model) if not minx: minx = x if not maxx: maxx = x if minx is None and maxx is None: if isinstance(x, datetime.datetime): minx, maxx = datetime.datetime.min, datetime.datetime.max elif isinstance(x, datetime.date): minx, maxx = datetime.date.min, datetime.date.max elif isinstance(x, datetime.timedelta): minx, maxx = datetime.timedelta.min, datetime.timedelta.max try: minx = min(minx, x) maxx = max(maxx, x) except TypeError: continue self.labels[x] = model[self.xfield['name']].get_client(model) self.ids.setdefault(x, []) self.ids[x].append(model.id) self.datas.setdefault(x, {}) for yfield in self.yfields: key = yfield.get('key', yfield['name']) if yfield.get('domain'): context = rpc.CONTEXT.copy() context['context'] = context.copy() context['_user'] = rpc._USER for field in model.group.fields: context[field] = model[field].get(model) if not PYSONDecoder(context).decode(yfield['domain']): continue self.datas[x].setdefault(key, 0.0) if yfield['name'] == '#': self.datas[x][key] += 1 else: value = model[yfield['name']].get(model) if isinstance(value, datetime.timedelta): value = value.total_seconds() self.datas[x][key] += float(value or 0) date_format = common.date_format( self.view.screen.context.get('date_format')) datetime_format = date_format + ' %X' if isinstance(minx, datetime.datetime): date = minx while date <= maxx: self.labels[date] = date.strftime(datetime_format) self.datas.setdefault(date, {}) for yfield in self.yfields: self.datas[date].setdefault( yfield.get('key', yfield['name']), 0.0) date += relativedelta(days=1) elif isinstance(minx, datetime.date): date = minx while date <= maxx: self.labels[date] = date.strftime(date_format) self.datas.setdefault(date, {}) for yfield in self.yfields: self.datas[date].setdefault( yfield.get('key', yfield['name']), 0.0) date += relativedelta(days=1) def updateArea(self, cr, width, height): maxylabel = '' for value, label in self.YLabels(): if len(maxylabel) < len(label): maxylabel = label extends = cr.text_extents(maxylabel) yLabelWidth = extends[2] maxxlabel = '' for value, label in self.XLabels(): if len(maxxlabel) < len(label): maxxlabel = label extends = cr.text_extents(maxxlabel) xLabelHeight = extends[3] if yLabelWidth > width / 3.0: yLabelWidth = 0 width = width - self.leftPadding - yLabelWidth - self.rightPadding height = height - self.topPadding - self.bottomPadding - xLabelHeight self.area = Area(self.leftPadding + yLabelWidth, self.topPadding, width, height) def updateXY(self): self.maxxval = len(self.datas) self.minxval = 0.0 self.xrange = self.maxxval - self.minxval if self.xrange == 0: self.xscale = 1.0 else: self.xscale = 1.0 / self.xrange if not list(self.datas.values()): self.maxyval = 0.0 self.minyval = 0.0 else: self.maxyval = max([reduce(lambda x, y: max(x, y), x.values()) for x in self.datas.values()]) self.minyval = min([reduce(lambda x, y: min(x, y), x.values()) for x in self.datas.values()]) if self.minyval > 0: self.minyval = 0.0 self.yrange = self.maxyval - self.minyval if self.yrange == 0: self.yscale = 1.0 else: self.yscale = 1.0 / self.yrange def updateGraph(self): pass def setColorScheme(self): keys = self._getDatasKeys() color = self.attrs.get('color', CONFIG['graph.color']) r, g, b = hex2rgb(COLOR_SCHEMES.get(color, color)) maxcolor = max(max(r, g), b) self.colorScheme = generateColorscheme(color, keys, maxcolor / (len(keys) or 1)) for yfield in self.yfields: if yfield.get('color'): self.colorScheme[yfield.get('key', yfield['name'])] = hex2rgb( COLOR_SCHEMES.get(yfield['color'], yfield['color'])) def _getDatasKeys(self): return [x.get('key', x['name']) for x in self.yfields] class Area(object): def __init__(self, x, y, w, h): self.x, self.y, self.w, self.h = x, y, w, h ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685599.0 tryton-7.0.24/tryton/gui/window/view_form/view/graph_gtk/line.py0000644000175000017500000002416714517761237023152 0ustar00cedced# This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. # This code is inspired by the pycha project # (http://www.lorenzogil.com/projects/pycha/) import datetime import locale import math import cairo import tryton.common as common import tryton.rpc as rpc from .graph import Graph class Line(Graph): def updateGraph(self): yfield2attrs = {} for yfield in self.yfields: yfield2attrs[yfield.get('key', yfield['name'])] = yfield self.points = [] i = 0 keys = list(self.datas.keys()) keys.sort() for xfield in keys: j = 0 for yfield in self.datas[xfield]: xval = i yval = self.datas[xfield][yfield] x = (xval - self.minxval) * self.xscale y = 1.0 - (yval - self.minyval) * self.yscale if self.xrange == 0: x = 1.0 if (not bool(int(yfield2attrs[yfield].get('empty', 1))) and yval == 0): continue point = Point(x, y, xval, yval, xfield, yfield) if (0.0 <= point.x <= 1.0) and (0.0 <= point.y <= 1.0): self.points.append(point) j += 1 i += 1 def drawGraph(self, cr, width, height): key2fill = {} key2interpolation = {} for yfield in self.yfields: key = yfield.get('key', yfield['name']) key2fill[key] = bool(int(yfield.get('fill', 0))) key2interpolation[key] = yfield.get('interpolation', 'linear') def preparePath(key): interpolation = key2interpolation[key] points = (p for p in self.points if p.yname == key) zero = 1.0 + self.minyval * self.yscale cr.new_path() cr.move_to(self.area.x, zero * self.area.h + self.area.y) if interpolation == 'linear': for point in points: cr.line_to(point.x * self.area.w + self.area.x, point.y * self.area.h + self.area.y) else: previous = Point(0, zero, None, None, None, None) def breakage(previous, point): if interpolation == 'constant-center': return previous.x + ((point.x - previous.x) / 2.0) elif interpolation == 'constant-left': return point.x elif interpolation == 'constant-right': return previous.x for point in points: cr.line_to( breakage(previous, point) * self.area.w + self.area.x, previous.y * self.area.h + self.area.y) cr.line_to( breakage(previous, point) * self.area.w + self.area.x, point.y * self.area.h + self.area.y) cr.line_to(point.x * self.area.w + self.area.x, point.y * self.area.h + self.area.y) previous = point cr.line_to(breakage(previous, Point(1, zero, None, None, None, None)) * self.area.w + self.area.x, previous.y * self.area.h + self.area.y) cr.line_to(self.area.w + self.area.x, zero * self.area.h + self.area.y) cr.move_to(self.area.x, zero * self.area.h + self.area.y) if key2fill[key]: cr.close_path() else: cr.set_source_rgb(*self.colorScheme[key]) cr.stroke() cr.save() cr.set_line_width(2) if self._getDatasKeys(): transparency = 0.8 / len(self._getDatasKeys()) for key in self._getDatasKeys(): if key2fill[key]: cr.save() r, g, b = self.colorScheme[key] preparePath(key) cr.set_source_rgb(r, g, b) cr.stroke_preserve() cr.restore() # Add soft transparency the area when line is filled cr.set_source_rgba(r, g, b, transparency) cr.fill() # Add gradient top to bottom linear = cairo.LinearGradient( width / 2, 0, width / 2, height) linear.add_color_stop_rgba( 0, r * 0.65, g * 0.65, b * 0.65, transparency) linear.add_color_stop_rgba(1, r, g, b, 0.1) cr.set_source(linear) preparePath(key) cr.fill() else: preparePath(key) for point in self.points: cr.set_source_rgb(*self.colorScheme[point.yname]) cr.move_to(point.x * self.area.w + self.area.x, point.y * self.area.h + self.area.y) cr.arc(point.x * self.area.w + self.area.x, point.y * self.area.h + self.area.y, 3, 0, 2 * math.pi) cr.fill() cr.restore() def drawLegend(self, cr, widht, height): super(Line, self).drawLegend(cr, widht, height) cr.save() for point in self.points: if point.highlight: cr.set_line_width(2) cr.set_source_rgb(*common.hex2rgb('#000000')) cr.move_to(point.x * self.area.w + self.area.x, point.y * self.area.h + self.area.y) cr.arc(point.x * self.area.w + self.area.x, point.y * self.area.h + self.area.y, 3, 0, 2 * math.pi) cr.stroke() cr.set_source_rgb(*common.highlight_rgb( *self.colorScheme[point.yname])) cr.arc(point.x * self.area.w + self.area.x, point.y * self.area.h + self.area.y, 3, 0, 2 * math.pi) cr.fill() cr.restore() def motion(self, widget, event): if not getattr(self, 'area', None): return nearest = None for point in self.points: x = point.x * self.area.w + self.area.x y = point.y * self.area.h + self.area.y square = (event.x - x) ** 2 + (event.y - y) ** 2 if not nearest or square < nearest[1]: nearest = (point, square) dia = self.area.w ** 2 + self.area.h ** 2 keys2txt = {} for yfield in self.yfields: keys2txt[yfield.get('key', yfield['name'])] = yfield['string'] highlight = False draw_points = [] yfields_timedelta = {x.get('key', x['name']): x.get('timedelta') for x in self.yfields if 'timedelta' in x} for point in self.points: if point == nearest[0] and nearest[1] < dia / 100: if not point.highlight: point.highlight = True label = keys2txt[point.yname] label += '\n' if point.yval in yfields_timedelta: converter = None if yfields_timedelta[point.yname]: converter = rpc.CONTEXT.get( yfields_timedelta[point.yname]) label += common.timedelta.format(point.yval, converter) else: label += locale.localize( '{:.2f}'.format(point.yval), True) label += '\n' label += str(self.labels[point.xname]) self.popup.set_text(label) draw_points.append(point) else: if point.highlight: point.highlight = False draw_points.append(point) if point.highlight: self.popup.set_position(self, point.x * self.area.w + self.area.x, point.y * self.area.h + self.area.y) highlight = True if highlight: self.popup.show() else: self.popup.hide() if draw_points: minx = self.area.w + self.area.x miny = self.area.h + self.area.y maxx = maxy = 0.0 for point in draw_points: x = self.area.w * point.x + self.area.x y = self.area.h * point.y + self.area.y minx = min(x - 5, minx) miny = min(y - 5, miny) maxx = max(x + 5, maxx) maxy = max(y + 5, maxy) self.queue_draw_area(int(minx - 1), int(miny - 1), int(maxx - minx + 1), int(maxy - miny + 1)) def updateXY(self): super(Line, self).updateXY() if self.xrange != 0: self.xrange -= 1 if self.xrange == 0: self.xscale = 1.0 else: self.xscale = 1.0 / self.xrange def drawAxis(self, cr, width, height): super(Line, self).drawAxis(cr, width, height) self.drawLine(cr, 1.0, 0) def action(self): super(Line, self).action() for point in self.points: if point.highlight: ids = self.ids[point.xname] self.action_keyword(ids) def YLabels(self): ylabels = super(Line, self).YLabels() if all('timedelta' in f for f in self.yfields): converter = {f.get('timedelta') for f in self.yfields} if len(converter) == 1: converter = rpc.CONTEXT.get(converter.pop()) return [ (x[0], common.timedelta.format( datetime.timedelta(seconds=locale.atof(x[1])), converter)) for x in ylabels] return ylabels class Point(object): def __init__(self, x, y, xval, yval, xname, yname): self.x, self.y = x, y self.xval, self.yval = xval, yval self.xname = xname self.yname = yname self.highlight = False ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685599.0 tryton-7.0.24/tryton/gui/window/view_form/view/graph_gtk/pie.py0000644000175000017500000001611114517761237022766 0ustar00cedced# This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. # This code is inspired by the pycha project # (http://www.lorenzogil.com/projects/pycha/) import datetime import locale import math import cairo import tryton.common as common import tryton.rpc as rpc from .graph import Area, Graph class Pie(Graph): def _getDatasKeys(self): return list(self.datas.keys()) def drawLegend(self, cr, width, height): pass def drawAxis(self, cr, width, height): cr.set_source_rgb(*common.hex2rgb('#000000')) for slice in self.slices: normalisedAngle = slice.normalisedAngle() labelx = self.centerx + \ math.sin(normalisedAngle) * (self.radius + 10) labely = self.centery - \ math.cos(normalisedAngle) * (self.radius + 10) label = '%s (%s%%)' % (self.labels[slice.xname], locale.localize('{:.2f}'.format(slice.fraction * 100))) extents = cr.text_extents(label) labelWidth = extents[2] labelHeight = extents[3] x = y = 0 if normalisedAngle <= math.pi * 0.5: x = labelx y = labely - labelHeight elif math.pi / 2 < normalisedAngle <= math.pi: x = labelx y = labely elif math.pi < normalisedAngle <= math.pi * 1.5: x = labelx - labelWidth y = labely else: x = labelx - labelWidth y = labely - labelHeight cr.move_to(x, y) cr.show_text(label) def drawLines(self, cr, width, height): pass def updateArea(self, cr, width, height): width = width - self.leftPadding - self.rightPadding height = height - self.topPadding - self.bottomPadding self.area = Area(self.leftPadding, self.topPadding, width, height) self.centerx = self.area.x + self.area.w * 0.5 self.centery = self.area.y + self.area.h * 0.5 self.radius = min(self.area.w * 0.4, self.area.h * 0.4) def updateGraph(self): self.sum = 0.0 for xkey in self.datas.keys(): key = self.yfields[0].get('key', self.yfields[0]['name']) if self.datas[xkey][key] > 0: self.sum += self.datas[xkey][key] fraction = angle = 0.0 self.slices = [] for xkey in self.datas.keys(): key = self.yfields[0].get('key', self.yfields[0]['name']) value = self.datas[xkey][key] if value > 0: angle += fraction fraction = value / self.sum slice = Slice(xkey, fraction, value, angle) self.slices.append(slice) def drawGraph(self, cr, width, height): cr.set_line_join(cairo.LINE_JOIN_ROUND) cr.save() for slice in self.slices: if slice.isBigEnough(): if bool(int(self.yfields[0].get('fill', 1))): color = self.colorScheme[slice.xname] if slice.highlight: color = common.highlight_rgb(*color) cr.set_source_rgba(*color) slice.draw(cr, self.centerx, self.centery, self.radius) cr.fill() cr.set_source_rgb(*common.hex2rgb( self.attrs.get('background', '#f5f5f5'))) slice.draw(cr, self.centerx, self.centery, self.radius) cr.set_line_width(2) cr.stroke() cr.restore() def motion(self, widget, event): super(Pie, self).motion(widget, event) if not getattr(self, 'area', None): return d = (event.x - self.centerx) ** 2 + (event.y - self.centery) ** 2 if d > self.radius ** 2: self.popup.hide() for slice in self.slices: if slice.highlight: self.queue_draw() slice.highlight = False return self.popup.show() if event.y == self.centery: angle = math.pi / 2 else: angle = math.atan((event.x - self.centerx) / (self.centery - event.y)) if event.x >= self.centerx: if event.y <= self.centery: pass else: angle += math.pi else: if event.y < self.centery: angle += 2 * math.pi else: angle += math.pi for slice in self.slices: if slice.startAngle <= angle <= slice.endAngle: if not slice.highlight: slice.highlight = True if 'timedelta' in self.yfields[0]: converter = self.yfields[0].get('timedelta') if converter: converter = rpc.CONTEXT.get(converter) value = common.timedelta.format( datetime.timedelta( seconds=slice.fraction * self.sum), converter) sum = common.timedelta.format( datetime.timedelta(seconds=self.sum), converter) else: value = locale.localize( '{:.2f}'.format(slice.fraction * self.sum)) sum = locale.localize('{:.2f}'.format(self.sum)) label = '%s (%s%%)\n%s/%s' % ( self.labels[slice.xname], locale.localize('{:.2f}'.format(slice.fraction * 100)), value, sum) self.popup.set_text(label) self.queue_draw() else: if slice.highlight: slice.highlight = False self.queue_draw() def action(self): super(Pie, self).action() for slice in self.slices: if slice.highlight: ids = self.ids[slice.xname] self.action_keyword(ids) class Slice(object): def __init__(self, xname, fraction, value, angle): self.xname = xname self.fraction = fraction self.value = value self.startAngle = 2 * angle * math.pi self.endAngle = 2 * (angle + fraction) * math.pi self.highlight = False def isBigEnough(self): return abs(self.startAngle - self.endAngle) > 0.001 def draw(self, cr, centerx, centery, radius): cr.new_path() cr.move_to(centerx, centery) cr.arc(centerx, centery, radius, self.startAngle - (math.pi / 2), self.endAngle - (math.pi / 2)) cr.line_to(centerx, centery) cr.close_path() def normalisedAngle(self): normalisedAngle = (self.startAngle + self.endAngle) / 2 if normalisedAngle > 2 * math.pi: normalisedAngle -= 2 * math.pi elif normalisedAngle < 0: normalisedAngle += 2 * math.pi return normalisedAngle ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1735581609.0 tryton-7.0.24/tryton/gui/window/view_form/view/list.py0000644000175000017500000014565214734557651021237 0ustar00cedced# This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. import gettext import json import locale import sys from functools import wraps from gi.repository import Gdk, GLib, GObject, Gtk from pygtkcompat.generictreemodel import GenericTreeModel import tryton.common as common from tryton.common import ( RPCException, RPCExecute, Tooltips, domain_inversion, node_attributes, simplify, unique_value) from tryton.common.cellrendererbutton import CellRendererButton from tryton.common.popup_menu import populate, popup from tryton.config import CONFIG from tryton.gui.window import Window from tryton.pyson import PYSONDecoder from . import View, XMLViewParser from .list_gtk.editabletree import EditableTreeView, TreeView from .list_gtk.widget import ( M2M, M2O, O2M, O2O, URL, Affix, Binary, Boolean, Button, Char, Date, Dict, Float, Image, Int, MultiSelection, ProgressBar, Reference, Selection, Text, Time, TimeDelta) _ = gettext.gettext def delay(func): """Decorator for ViewTree method to delay execution when idle and if display counter did not change""" @wraps(func) def wrapper(self, *args, **kwargs): def wait(): if not self.treeview.props.window: return if self.treeview.display_counter == display_counter: func(self, *args, **kwargs) display_counter = self.treeview.display_counter GLib.idle_add(wait) return wrapper def path_convert_id2pos(model, id_path): "This function will transform a path of id into a path of position" group = model.group id_path = id_path[:] indexes = [] while id_path: current_id = id_path.pop(0) try: record = group.get(current_id) indexes.append(group.index(record)) group = record.children_group(model.children_field) except (KeyError, AttributeError, ValueError): return None return tuple(indexes) class AdaptModelGroup(GenericTreeModel): def __init__(self, group, children_field=None): super(AdaptModelGroup, self).__init__() self.group = group self.set_property('leak_references', False) self.children_field = children_field self.__removed = None # XXX dirty hack to allow update of has_child def added(self, group, record): if (group is self.group and (record.group is self.group or record.group.child_name == self.children_field)): path = record.get_index_path(self.group) iter_ = self.get_iter(path) self.row_inserted(path, iter_) if record.children_group(self.children_field): self.row_has_child_toggled(path, iter_) if (record.parent and record.group is not self.group): path = record.parent.get_index_path(self.group) iter_ = self.get_iter(path) self.row_has_child_toggled(path, iter_) def removed(self, group, record): if (group is self.group and (record.group is self.group or record.group.child_name == self.children_field)): path = record.get_index_path(self.group) self.row_deleted(path) def append(self, model): self.group.add(model) def prepend(self, model): self.group.add(model, 0) def remove(self, iter_): record = self.get_value(iter_, 0) record.group.remove(record) self.invalidate_iters() def __move(self, record, path, offset=0): iter_ = self.get_iter(path) record_pos = self.get_value(iter_, 0) group = record_pos.group pos = group.index(record_pos) + offset if group is not record.group: prev_group = record.group record.group.remove(record, remove=True, force_remove=True) # Don't remove record from previous group # as the new parent will change the parent # This prevents concurrency conflict record.group.record_removed.remove(record) group.add(record) if not record.parent_name: record.modified_fields.setdefault(prev_group.parent_name) record.value[prev_group.parent_name] = None else: record.modified_fields.setdefault(record.parent_name) group.move(record, pos) def move_before(self, record, path): self.__move(record, path) def move_after(self, record, path): self.__move(record, path, 1) def move_into(self, record, path): iter_ = self.get_iter(path) parent = self.get_value(iter_, 0) group = parent.children_group(self.children_field) if group is not record.group: record.group.remove(record, remove=True, force_remove=True) # Don't remove record from previous group # as the new parent will change the parent # This prevents concurrency conflict if record in record.group.record_removed: record.group.record_removed.remove(record) group.add(record) record.modified_fields.setdefault(record.parent_name or 'id') group.move(record, 0) def sort(self, ids): old_idx = {record.id: i for i, record in enumerate(self.group)} new_idx = {id_: i for i, id_ in enumerate(ids)} size = len(self.group) self.group.sort(key=lambda r: new_idx.get(r.id, size)) new_order = [] prev = None for record in self.group: new_order.append(old_idx.get(record.id)) if prev: prev.next[id(self.group)] = record prev = record if prev: prev.next[id(self.group)] = None path = Gtk.TreePath() # XXX pygobject does not allow to create empty TreePath, # it is always a path of 0 # see: https://bugzilla.gnome.org/show_bug.cgi?id=770665 if hasattr(path, 'get_depth'): while path.get_depth(): path.up() self.rows_reordered(path, None, new_order) def __len__(self): return len(self.group) def on_get_flags(self): if not self.children_field: return Gtk.TreeModelFlags.LIST_ONLY return 0 def on_get_n_columns(self): # XXX return 1 def on_get_column_type(self, index): # XXX return GObject.TYPE_PYOBJECT def on_get_path(self, record): return record.get_index_path(self.group) def on_get_iter(self, path): group = self.group record = None for i in path: if group is None or i >= len(group): return None record = group[i] if not self.children_field: break group = record.children_group(self.children_field) return record def on_get_value(self, record, column): return record def on_iter_next(self, record): if record is None: return None return record.next.get(id(record.group)) def on_iter_has_child(self, record): if record is None or not self.children_field: return False children = record.children_group(self.children_field) if children is None: return False length = len(children) if self.__removed and self.__removed in children: length -= 1 return bool(length) def on_iter_children(self, record): if record is None: if self.group: return self.group[0] else: return None if self.children_field: children = record.children_group(self.children_field) if children: return children[0] return None def on_iter_n_children(self, record): if record is None: return len(self.group) if not self.children_field: return 0 return len(record.children_group(self.children_field)) def on_iter_nth_child(self, record, nth): if record is None: if nth < len(self.group): return self.group[nth] return None if not self.children_field: return None if nth < len(record.children_group(self.children_field)): return record.children_group(self.children_field)[nth] return None def on_iter_parent(self, record): if record is None: return None return record.parent class TreeXMLViewParser(XMLViewParser): WIDGETS = { 'binary': Binary, 'boolean': Boolean, 'callto': URL, 'char': Char, 'date': Date, 'dict': Dict, 'email': URL, 'float': Float, 'image': Image, 'integer': Int, 'many2many': M2M, 'many2one': M2O, 'numeric': Float, 'one2many': O2M, 'one2one': O2O, 'progressbar': ProgressBar, 'reference': Reference, 'selection': Selection, 'multiselection': MultiSelection, 'sip': URL, 'text': Text, 'time': Time, 'timedelta': TimeDelta, 'url': URL, } def _parse_tree(self, node, attributes): for child in node.childNodes: self.parse(child) def _parse_field(self, node, attributes): name = attributes['name'] widget = self.WIDGETS[attributes['widget']](self.view, attributes) self.view.widgets[name].append(widget) column = Gtk.TreeViewColumn(attributes['string']) column._type = 'field' column.name = name prefixes = [] suffixes = list(widget.suffixes) if attributes['widget'] in ['url', 'email', 'callto', 'sip']: prefixes.append( Affix(self.view, attributes, protocol=attributes['widget'])) if 'icon' in attributes: prefixes.append(Affix(self.view, attributes)) for affix in node.childNodes: affix_attrs = node_attributes(affix) affix_attrs['visual'] = attributes.get('visual') if 'name' not in affix_attrs: affix_attrs['name'] = attributes['name'] if affix.tagName == 'prefix': list_ = prefixes else: list_ = suffixes list_.append(Affix(self.view, affix_attrs)) prefixes.extend(widget.prefixes) for prefix in prefixes: column.pack_start(prefix.renderer, expand=prefix.expand) column.set_cell_data_func(prefix.renderer, prefix.setter) column.pack_start(widget.renderer, expand=True) column.set_cell_data_func(widget.renderer, widget.setter) for suffix in suffixes: column.pack_start(suffix.renderer, expand=suffix.expand) column.set_cell_data_func(suffix.renderer, suffix.setter) self._set_column_widget(column, attributes, align=widget.align) self._set_column_width(column, attributes) if (not self.view.attributes.get('sequence') and not self.view.children_field and self.field_attrs[name].get('sortable', True)): column.connect('clicked', self.view.sort_model) self.view.treeview.append_column(column) if 'optional' in attributes and name != self.exclude_field: self.view.optionals.append(column) def _parse_button(self, node, attributes): button = Button(self.view, attributes) self.view.state_widgets.append(button) column = Gtk.TreeViewColumn( attributes.get('string', ''), button.renderer) column._type = 'button' column.name = None column.set_cell_data_func(button.renderer, button.setter) self._set_column_widget(column, attributes, arrow=False) self._set_column_width(column, attributes) decoder = PYSONDecoder(self.view.screen.context) column.set_visible( not decoder.decode(attributes.get('tree_invisible', '0'))) self.view.treeview.append_column(column) def _set_column_widget(self, column, attributes, arrow=True, align=0.5): hbox = Gtk.HBox(homogeneous=False, spacing=2) label = Gtk.Label(label=attributes['string']) field = self.field_attrs.get(attributes['name'], {}) if field and self.view.editable: required = field.get('required') readonly = field.get('readonly') common.apply_label_attributes(label, readonly, required) label.show() help = attributes.get('help') if help: tooltips = Tooltips() tooltips.set_tip(label, help) tooltips.enable() if arrow: arrow_widget = Gtk.Image() arrow_widget.show() column.arrow = arrow_widget hbox.pack_start(label, expand=True, fill=True, padding=0) if arrow: hbox.pack_start(arrow_widget, expand=False, fill=False, padding=0) column.set_clickable(True) hbox.show() if bool(int(attributes.get('sum', 0))): vbox = Gtk.VBox(homogeneous=False, spacing=0) vbox.pack_start(hbox, expand=False, fill=True, padding=0) sum_ = Gtk.Label() sum_.set_justify(Gtk.Justification.RIGHT) sum_.show() vbox.pack_start(sum_, expand=False, fill=True, padding=0) self.view.sum_widgets.append((attributes['name'], sum_)) vbox.show() widget = vbox else: widget = hbox column.set_widget(widget) column.set_alignment(align) def _set_column_width(self, column, attributes): default_width = { 'integer': 80, 'selection': 90, 'reference': 200, 'one2many': 50, 'many2many': 50, 'boolean': 30, 'binary': 200, } screen = self.view.screen width = screen.tree_column_width[screen.model_name].get(column.name) field_attrs = self.field_attrs.get(attributes['name'], {}) if not width: if 'width' in attributes: width = int(attributes['width']) elif field_attrs: width = default_width.get(field_attrs['type'], 100) if attributes.get('symbol'): width += 20 else: width = 80 column.width = width if width > 0: column.set_fixed_width(width) column.set_min_width(1) expand = bool(attributes.get('expand', False)) column.set_expand(expand) column.set_resizable(True) if attributes.get('widget') != 'text': column.set_sizing(Gtk.TreeViewColumnSizing.FIXED) class ViewTree(View): view_type = 'tree' xml_parser = TreeXMLViewParser draggable = False def __init__(self, view_id, screen, xml, children_field): self.children_field = children_field self.optionals = [] self.sum_widgets = [] self.sum_box = Gtk.HBox() self.treeview = None self._editable = bool(int(xml.getAttribute('editable') or 0)) self._creatable = bool(int(xml.getAttribute('creatable') or 1)) if self._editable: self.treeview = EditableTreeView(self) grid_lines = Gtk.TreeViewGridLines.BOTH else: self.treeview = TreeView(self) grid_lines = Gtk.TreeViewGridLines.VERTICAL super().__init__(view_id, screen, xml) self.set_drag_and_drop() self.mnemonic_widget = self.treeview self.treeview.set_property('enable-grid-lines', grid_lines) self.treeview.set_fixed_height_mode( all(c.get_sizing() == Gtk.TreeViewColumnSizing.FIXED for c in self.treeview.get_columns())) self.treeview.connect('button-press-event', self.__button_press) self.treeview.connect('key-press-event', self.on_keypress) self.treeview.connect_after('row-activated', self.__sig_switch) if self.children_field: self.treeview.connect('test-expand-row', self.test_expand_row) self.treeview.connect('row-expanded', self._row_expanded) self.treeview.connect('row-collapsed', self._row_collapsed) self.treeview.set_rubber_banding(True) selection = self.treeview.get_selection() selection.set_mode(Gtk.SelectionMode.MULTIPLE) selection.connect('changed', self.__select_changed) self.widget = Gtk.VBox() self.scroll = scroll = Gtk.ScrolledWindow() scroll.add(self.treeview) scroll.set_policy( Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC) scroll.set_placement(Gtk.CornerType.TOP_LEFT) viewport = Gtk.Viewport() viewport.set_shadow_type(Gtk.ShadowType.ETCHED_IN) viewport.add(scroll) self.widget.pack_start(viewport, expand=True, fill=True, padding=0) self.sum_box.show() self.widget.pack_start( self.sum_box, expand=False, fill=False, padding=0) self.treeview.set_search_equal_func(self.search_equal_func) self.display() if self.optionals: if self.draggable: column = self.treeview.get_columns()[0] else: column = Gtk.TreeViewColumn() column.set_sizing(Gtk.TreeViewColumnSizing.FIXED) column.name = None column._type = 'optional' self.treeview.insert_column(column, 0) image = Gtk.Image() image.set_from_pixbuf(common.IconFactory.get_pixbuf('tryton-menu')) image.show() column.set_widget(image) column.set_fixed_width(25) column.set_clickable(True) column.connect('clicked', self.optional_menu) # Add last column if necessary after display for updated visible for column in self.treeview.get_columns(): if column.get_expand() and column.get_visible(): break else: column = Gtk.TreeViewColumn() column._type = 'fill' column.name = None column.set_sizing(Gtk.TreeViewColumnSizing.FIXED) self.treeview.append_column(column) def optional_menu(self, column): def toggle(menuitem, column): column.set_visible(menuitem.get_active()) self.save_optional() widget = column.get_widget() menu = Gtk.Menu() for optional in self.optionals: menuitem = Gtk.CheckMenuItem(label=optional.get_title()) menuitem.set_active(optional.get_visible()) menuitem.connect('toggled', toggle, optional) menu.add(menuitem) popup(menu, widget) def save_optional(self): fields = {c.name: not c.get_visible() for c in self.optionals} try: RPCExecute( 'model', 'ir.ui.view_tree_optional', 'set_optional', self.view_id, fields) except RPCException: pass self.screen.tree_column_optional[self.view_id] = fields def get_column_widget(self, column): 'Return the widget of the column' idx = [c for c in self.treeview.get_columns() if c.name == column.name].index(column) return self.widgets[column.name][idx] def search_equal_func(self, model, column, key, iter_): key = key.lower() record = model.get_value(iter_, 0) for field_name in self.get_fields(visible_only=True): field = record[field_name] field.state_set(record, states=('invisible',)) if not field.get_state_attrs(record).get('invisible', False): for widget in self.widgets[field_name]: text = str(widget.get_textual_value(record)).lower() # Simple search by word while text: if text.startswith(key): return False text = ' '.join(text.split()[1:]) return True def sort_model(self, column): up = common.IconFactory.get_pixbuf('tryton-arrow-up') down = common.IconFactory.get_pixbuf('tryton-arrow-down') for col in self.treeview.get_columns(): if col != column and getattr(col, 'arrow', None): col.arrow.clear() self.screen.order = self.screen.default_order if not column.arrow.props.pixbuf: column.arrow.set_from_pixbuf(down) self.screen.order = [(column.name, 'ASC')] else: if column.arrow.props.pixbuf == down: column.arrow.set_from_pixbuf(up) self.screen.order = [(column.name, 'DESC')] else: column.arrow.clear() model = self.treeview.get_model() unsaved_records = [x for x in model.group if x.id < 0] search_string = self.screen.screen_container.get_text() or '' if (self.screen.search_count == len(model) or unsaved_records or self.screen.parent): ids = self.screen.search_filter( search_string=search_string, only_ids=True) model.sort(ids) else: self.screen.search_filter(search_string=search_string) def update_arrow(self): order = self.screen.order if order and len(order) == 1: (name, direction), = order if direction: direction = direction.split(None, 1)[0] direction = { 'ASC': common.IconFactory.get_pixbuf('tryton-arrow-down'), 'DESC': common.IconFactory.get_pixbuf('tryton-arrow-up'), }[direction] else: name, direction = None, None for col in self.treeview.get_columns(): arrow = getattr(col, 'arrow', None) if arrow: if col.name != name: arrow.clear() else: if direction: arrow.set_from_pixbuf(direction) else: arrow.clear() def set_drag_and_drop(self): dnd = False if self.children_field: children = self.group.fields.get(self.children_field) if children: parent_name = children.attrs.get('relation_field') dnd = parent_name in self.widgets elif self.attributes.get('sequence'): dnd = True # Disable DnD on mac until it is fully supported if sys.platform == 'darwin': dnd = False if self.screen.readonly: dnd = False self.draggable = dnd if not dnd: return self.treeview.enable_model_drag_dest( [('MY_TREE_MODEL_ROW', Gtk.TargetFlags.SAME_WIDGET, 0)], Gdk.DragAction.MOVE) self.treeview.enable_model_drag_source( Gdk.ModifierType.BUTTON1_MASK | Gdk.ModifierType.BUTTON3_MASK, [('MY_TREE_MODEL_ROW', Gtk.TargetFlags.SAME_WIDGET, 0)], Gdk.DragAction.MOVE) # XXX have to set manually because enable_model_drag_source # does not set the mask # https://bugzilla.gnome.org/show_bug.cgi?id=756177 self.treeview.drag_source_set( Gdk.ModifierType.BUTTON1_MASK | Gdk.ModifierType.BUTTON3_MASK, [Gtk.TargetEntry.new( 'MY_TREE_MODEL_ROW', Gtk.TargetFlags.SAME_WIDGET, 0)], Gdk.DragAction.MOVE) self.treeview.connect("drag-data-get", self.drag_data_get) self.treeview.connect('drag-data-received', self.drag_data_received) self.treeview.connect('drag-drop', self.drag_drop) self.treeview.connect('drag-data-delete', self.drag_data_delete) drag_column = Gtk.TreeViewColumn() drag_column.set_sizing(Gtk.TreeViewColumnSizing.FIXED) drag_column._type = 'drag' drag_column.name = None cell_pixbuf = Gtk.CellRendererPixbuf() cell_pixbuf.props.pixbuf = common.IconFactory.get_pixbuf('tryton-drag') drag_column.pack_start(cell_pixbuf, expand=False) self.treeview.insert_column(drag_column, 0) @property def modified(self): return False @property def editable(self): return self._editable and not self.screen.readonly @property def creatable(self): return self.editable and self._creatable def get_fields(self, visible_only=False): return [col.name for col in self.treeview.get_columns() if col.name and (not visible_only or col.get_visible())] def get_buttons(self): return [b for b in self.state_widgets if isinstance(b.renderer, CellRendererButton)] def on_keypress(self, widget, event): control_mask = Gdk.ModifierType.CONTROL_MASK if sys.platform == 'darwin': control_mask = Gdk.ModifierType.MOD2_MASK if (event.keyval == Gdk.KEY_c and event.state & control_mask): self.on_copy() return False if (event.keyval == Gdk.KEY_v and event.state & control_mask): self.on_paste() return False def test_expand_row(self, widget, iter_, path): model = widget.get_model() if model.iter_n_children(iter_) > CONFIG['client.limit']: self.record = model.get_value(iter_, 0) self.screen.switch_view('form') return True return False def _row_expanded(self, treeview, iter_, path): # Force record_message self.screen.current_record = self.screen.current_record def _row_collapsed(self, treeview, iter_, path): # Force record_message self.screen.current_record = self.screen.current_record model = treeview.get_model() iter_ = model.iter_children(iter_) while iter_: record = model.get_value(iter_, 0) if record.exception: record.cancel() iter_ = model.iter_next(iter_) def on_copy(self): for clipboard_type in [ Gdk.SELECTION_CLIPBOARD, Gdk.SELECTION_PRIMARY]: clipboard = self.treeview.get_clipboard(clipboard_type) selection = self.treeview.get_selection() data = [] selection.selected_foreach(self.copy_foreach, data) clipboard.set_text('\n'.join(data), -1) def copy_foreach(self, treemodel, path, iter, data): record = treemodel.get_value(iter, 0) values = [] for col in self.treeview.get_columns(): if not col.get_visible() or not col.name: continue widget = self.get_column_widget(col) values.append('"' + str(widget.get_textual_value(record)).replace('"', '""') + '"') data.append('\t'.join(values)) return def on_paste(self): if not self.editable: return def unquote(value): if value[:1] == '"' and value[-1:] == '"': return value[1:-1] return value data = [] for clipboard_type in [ Gdk.SELECTION_CLIPBOARD, Gdk.SELECTION_PRIMARY]: clipboard = self.treeview.get_clipboard(clipboard_type) text = clipboard.wait_for_text() if not text: continue data = [[unquote(v) for v in l.split('\t')] for l in text.splitlines()] break else: return col = self.treeview.get_cursor()[1] columns = [c for c in self.treeview.get_columns() if c.get_visible() and c.name] if col in columns: idx = columns.index(col) columns = columns[idx:] if self.record: record = self.record group = record.group idx = group.index(record) else: group = self.group idx = len(group) default = None for line in data: if idx >= len(group): record = group.new(default=False) if default is None: default = record.default_get() record.set_default(default) group.add(record) record = group[idx] for col, value in zip(columns, line): widget = self.get_column_widget(col) if widget.get_textual_value(record) != value: if hasattr(widget, 'search_remote'): field = record.group.fields[widget.attrs['name']] win = widget.search_remote(record, field, value) if len(win.screen.group) == 1: target, = win.screen.group field.set_client( record, (target.id, target.rec_name())) else: field.set_client(record, (None, '')) win.response(win, Gtk.ResponseType.CANCEL) else: widget.value_from_text(record, value) if value and not widget.get_textual_value(record): # Stop setting value if a value is correctly set idx = len(group) break if not record.validate(): break record.save() idx += 1 self.record = record self.screen.display(set_cursor=True) def drag_data_get(self, treeview, context, selection, target_id, etime): treeview.stop_emission_by_name('drag-data-get') def _func_sel_get(model, path, iter_, data): value = model.get_value(iter_, 0) data.append(json.dumps( value.get_path(model.group), separators=(',', ':'))) data = [] treeselection = treeview.get_selection() treeselection.selected_foreach(_func_sel_get, data) if not data: return selection.set(selection.get_target(), 8, data[0].encode('utf-8')) return True def drag_data_received(self, treeview, context, x, y, selection, info, etime): treeview.stop_emission_by_name('drag-data-received') if self.attributes.get('sequence'): field = self.group.fields[self.attributes['sequence']] for record in self.group: if field.get_state_attrs( record).get('readonly', False): return try: selection_data = selection.data except AttributeError: selection_data = selection.get_data() if not selection_data: return selection_data = selection_data.decode('utf-8') # Don't received if the treeview was editing because it breaks the # internal state of the cursor. cursor, column = treeview.get_cursor() if column: for renderer in column.get_cells(): if renderer.props.editing: return model = treeview.get_model() try: data = json.loads(selection_data) except ValueError: return record = model.group.get_by_path(data) record_path = record.get_index_path(model.group) drop_info = treeview.get_dest_row_at_pos(x, y) def check_recursion(from_, to): if not from_ or not to: return True if from_ == to: return False length = min(len(from_), len(to)) if len(from_) < len(to) and from_[:length] == to[:length]: return False return True if drop_info: path, position = drop_info check_path = tuple(path) if position in [ Gtk.TreeViewDropPosition.BEFORE, Gtk.TreeViewDropPosition.AFTER]: check_path = path[:-1] if not check_recursion(record_path, check_path): return if position == Gtk.TreeViewDropPosition.BEFORE: model.move_before(record, path) elif position == Gtk.TreeViewDropPosition.AFTER: model.move_after(record, path) elif self.children_field: model.move_into(record, path) else: model.move_after(record, (len(model) - 1,)) Gdk.drop_finish(context, False, etime) selection = self.treeview.get_selection() selection.unselect_all() selection.select_path(record.get_index_path(model.group)) if self.attributes.get('sequence'): record.group.set_sequence( field=self.attributes['sequence'], position=self.screen.new_position) return True def drag_drop(self, treeview, context, x, y, time): treeview.stop_emission_by_name('drag-drop') targets = treeview.drag_dest_get_target_list() target = treeview.drag_dest_find_target(context, targets) treeview.drag_get_data(context, target, time) return True def drag_data_delete(self, treeview, context): treeview.stop_emission_by_name('drag-data-delete') def __button_press(self, treeview, event): if event.button == 3: try: path, col, x, y = treeview.get_path_at_pos( int(event.x), int(event.y)) except TypeError: # Outside row return False menu = Gtk.Menu() copy_item = Gtk.MenuItem(label=_('Copy')) copy_item.connect('activate', lambda x: self.on_copy()) menu.append(copy_item) if self.editable: paste_item = Gtk.MenuItem(label=_('Paste')) paste_item.connect('activate', lambda x: self.on_paste()) menu.append(paste_item) def pop(menu, group, record): # Don't activate actions if parent is modified parent = record.parent if record else None while parent: if parent.modified: break parent = parent.parent else: populate(menu, group.model_name, record) for col in self.treeview.get_columns(): if not col.get_visible() or not col.name: continue field = group.fields[col.name] model = None if field.attrs['type'] == 'many2one': model = field.attrs['relation'] record_id = field.get(record) elif field.attrs['type'] == 'reference': value = field.get(record) if value: model, record_id = value.split(',') record_id = int(record_id) if not model: continue label = field.attrs['string'] context = field.get_context(record) populate( menu, model, record_id, title=label, field=field, context=context) selection = treeview.get_selection() if selection.count_selected_rows() == 1: group = self.group if selection.get_mode() == Gtk.SelectionMode.SINGLE: model = selection.get_selected()[0] elif selection.get_mode() == Gtk.SelectionMode.MULTIPLE: model = selection.get_selected_rows()[0] record = model.get_value(model.get_iter(path), 0) pop(menu, group, record) menu.show_all() if hasattr(menu, 'popup_at_pointer'): menu.popup_at_pointer(event) else: menu.popup(None, None, None, event.button, event.time) return True # Don't change the selection elif event.button == 2: with Window(allow_similar=True): self.screen.row_activate() return True return False def group_list_changed(self, group, action, *args): model = self.treeview.get_model() if model is not None: if action == 'record-added': record, pos = args model.added(group, record) elif action == 'record-removed': record, pos = args model.removed(group, record) self.display(force=action == 'group-cleared') def __str__(self): return 'ViewList (%d)' % id(self) def __getitem__(self, name): return None def save_width(self): if not CONFIG['client.save_tree_width']: return fields = {} last_col = None for col in self.treeview.get_columns(): if col.get_visible(): last_col = col if not hasattr(col, 'name') or not hasattr(col, 'width'): continue if (col.get_width() != col.width and col.get_visible() and not col.get_expand()): fields[col.name] = col.get_width() # Don't set width for last visible columns # as it depends of the screen size if last_col and last_col.name in fields: del fields[last_col.name] if fields and any(fields.values()): model_name = self.screen.model_name try: RPCExecute('model', 'ir.ui.view_tree_width', 'set_width', model_name, fields) except RPCException: pass self.screen.tree_column_width[model_name].update(fields) def destroy(self): self.save_width() self.treeview.destroy() def __sig_switch(self, treeview, path, column): if column._type == 'button': return allow_similar = False event = Gtk.get_current_event() if (event.state & Gdk.ModifierType.MOD1_MASK or event.state & Gdk.ModifierType.SHIFT_MASK): allow_similar = True with Window(allow_similar=allow_similar): if not self.screen.row_activate() and self.children_field: if treeview.row_expanded(path): treeview.collapse_row(path) else: treeview.expand_row(path, False) def __select_changed(self, tree_sel): previous_record = self.record if (previous_record and (previous_record not in previous_record.group or previous_record.destroyed)): previous_record = None if tree_sel.get_mode() == Gtk.SelectionMode.SINGLE: model, iter_ = tree_sel.get_selected() if model and iter_: record = model.get_value(iter_, 0) self.record = record else: self.record = None elif tree_sel.get_mode() == Gtk.SelectionMode.MULTIPLE: model, paths = tree_sel.get_selected_rows() if model and paths: records = [] for path in paths: iter_ = model.get_iter(path) records.append(model.get_value(iter_, 0)) if self.record not in records: self.record = records[0] else: self.record = None if self.editable and previous_record: def go_previous(): self.record = previous_record self.set_cursor() if not self.screen.parent and previous_record != self.record: def save(): if not previous_record.destroyed: if not previous_record.save(): go_previous() if not previous_record.validate(self.get_fields()): go_previous() return True # Delay the save to let GTK process the current event GLib.idle_add(save) elif previous_record != self.record and self.screen.pre_validate: def pre_validate(): if not previous_record.destroyed: if not previous_record.pre_validate(): go_previous() # Delay the pre_validate to let GTK process the current event GLib.idle_add(pre_validate) self.update_sum() def set_value(self): if self.editable: self.treeview.set_value() def reset(self): pass def display(self, force=False): self.treeview.display_counter += 1 current_record = self.record if current_record and current_record not in current_record.group: # current record may have been removed by on_change calls without # changing the current record of screen before the display current_record = None if (force or not self.treeview.get_model() or self.group != self.treeview.get_model().group): model = AdaptModelGroup(self.group, self.children_field) self.treeview.set_model(model) # __select_changed resets current_record to None self.record = current_record if current_record: selection = self.treeview.get_selection() path = current_record.get_index_path(model.group) selection.select_path(path) # The search column must be set each time the model is changed self.treeview.set_search_column(0) if not current_record: selection = self.treeview.get_selection() selection.unselect_all() self.treeview.queue_draw() if self.editable: self.set_state() self.update_arrow() self.update_sum() # Set column visibility depending on attributes and domain domain = [] if self.screen.domain: domain.append(self.screen.domain) tab_domain = self.screen.screen_container.get_tab_domain() if tab_domain: domain.append(tab_domain) domain = simplify(domain) decoder = PYSONDecoder(self.screen.context) tree_column_optional = self.screen.tree_column_optional.get( self.view_id, {}) for column in self.treeview.get_columns(): name = column.name if not name: continue widget = self.get_column_widget(column) widget.set_editable() if column.name in tree_column_optional: optional = tree_column_optional[column.name] else: optional = bool(int(widget.attrs.get('optional', '0'))) invisible = decoder.decode(widget.attrs.get('tree_invisible', '0')) if invisible or optional: column.set_visible(False) elif name == self.screen.exclude_field: column.set_visible(False) elif name in self.group.fields: field = self.group.fields[name] inv_domain = domain_inversion(domain, name) if not isinstance(inv_domain, bool): inv_domain = simplify(inv_domain) unique, _, _ = unique_value( inv_domain, single_value=field._single_value) column.set_visible(not unique or bool(self.children_field)) if self.children_field: for i, column in enumerate(self.treeview.get_columns()): if (self.draggable or self.optionals) and not i: continue if column.get_visible(): self.treeview.set_expander_column(column) break def set_state(self): record = self.record if record: for field in record.group.fields: field = record.group.fields.get(field, None) if field: field.state_set(record) @delay def update_sum(self): selected_records = self.selected_records for name, label in self.sum_widgets: sum_ = None selected_sum = None loaded = True digit = 0 field = self.group.fields[name] for record in self.group: if not record.get_loaded([name]) and record.id >= 0: loaded = False break value = field.get(record) if value is not None: if sum_ is None: sum_ = value else: sum_ += value if record in selected_records or not selected_records: if selected_sum is None: selected_sum = value else: selected_sum += value if hasattr(field, 'digits'): fdigits = field.digits(record) if fdigits and digit is not None: digit = max(fdigits[1], digit) else: digit = None if loaded: if field.attrs['type'] == 'timedelta': converter = field.converter(self.group) selected_sum = common.timedelta.format( selected_sum, converter) sum_ = common.timedelta.format(sum_, converter) elif digit is not None: selected_sum = locale.localize( '{0:.{1}f}'.format(selected_sum or 0, digit), True) sum_ = locale.localize( '{0:.{1}f}'.format(sum_ or 0, digit), True) else: selected_sum = locale.localize( '{}'.format(selected_sum or 0), True) sum_ = locale.localize('{}'.format(sum_ or 0), True) text = '%s\n%s' % (selected_sum, sum_) else: text = '-' label.set_text(text) def set_cursor(self, new=False, reset_view=True): self.treeview.grab_focus() model = self.treeview.get_model() if self.record and model and self.treeview.get_realized(): path = self.record.get_index_path(model.group) if model.get_flags() & Gtk.TreeModelFlags.LIST_ONLY: path = (path[0],) focus_column, focus_cell = self.treeview.next_column( path, editable=new) if path[:-1]: self.treeview.expand_to_path(Gtk.TreePath(path[:-1])) self.treeview.scroll_to_cell(path, focus_column, use_align=False) current_path = self.treeview.get_cursor()[0] selected_path = \ self.treeview.get_selection().get_selected_rows()[1] path = Gtk.TreePath(path) if (current_path != path and path not in selected_path) or new: self.treeview.set_cursor(path, focus_column, start_editing=new) @property def selected_records(self): def _func_sel_get(model, path, iter_, records): records.append(model.get_value(iter_, 0)) records = [] sel = self.treeview.get_selection() sel.selected_foreach(_func_sel_get, records) return records def get_selected_paths(self): selection = self.treeview.get_selection() model, rows = selection.get_selected_rows() id_paths = [] for row in rows: path = () id_path = [] for node in row: path += (node,) iter_ = model.get_iter(path) id_path.append(model.get_value(iter_, 0).id) id_paths.append(id_path) return id_paths @property def listed_records(self): model = self.treeview.get_model() if not self.children_field: return list(model.group) def get_listed_records(start): records = [] if start: iter_ = model.get_iter(Gtk.TreePath(start)) else: iter_ = None for idx in range(model.iter_n_children(iter_)): path = start + (idx,) iter_ = model.get_iter(path) record = model.get_value(iter_, 0) records.append(record) if self.treeview.row_expanded(Gtk.TreePath(path)): records += get_listed_records(path) return records return get_listed_records(()) def get_listed_paths(self): model = self.treeview.get_model() if not self.children_field: return [[r.id] for r in model.group] def get_listed_paths(start=None, start_path=None): paths = [] if start: iter_ = model.get_iter(Gtk.TreePath(start)) else: iter_ = None for idx in range(model.iter_n_children(iter_)): path = start + (idx,) iter_ = model.get_iter(path) record = model.get_value(iter_, 0) id_path = start_path + [record.id] paths.append(id_path) if self.treeview.row_expanded(Gtk.TreePath(path)): paths += get_listed_paths(path, id_path) return paths return get_listed_paths((), []) def select_nodes(self, nodes): selection = self.treeview.get_selection() if not nodes: return selection.unselect_all() scroll = False model = self.treeview.get_model() for node in nodes: path = path_convert_id2pos(model, node) if path: selection.select_path(Gtk.TreePath(path)) if not scroll: self.treeview.scroll_to_cell(path) scroll = True def get_expanded_paths(self, starting_path=None, starting_id_path=None): # Use id instead of position # because the position may change between load if not starting_path: starting_path = tuple() if not starting_id_path: starting_id_path = [] id_paths = [] model = self.treeview.get_model() if starting_path: iter_ = model.get_iter(Gtk.TreePath(starting_path)) else: iter_ = None for path_idx in range(model.iter_n_children(iter_)): path = starting_path + (path_idx,) expanded = self.treeview.row_expanded(Gtk.TreePath(path)) if expanded: iter_ = model.get_iter(path) expanded_record = model.get_value(iter_, 0) id_path = starting_id_path + [expanded_record.id] id_paths.append(id_path) child_id_paths = self.get_expanded_paths(path, id_path) id_paths += child_id_paths return id_paths def expand_nodes(self, nodes): model = self.treeview.get_model() for node in nodes: expand_path = path_convert_id2pos(model, node) if expand_path: self.treeview.expand_to_path(Gtk.TreePath(expand_path)) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685599.0 tryton-7.0.24/tryton/gui/window/view_form/view/list_form.py0000644000175000017500000001551014517761237022243 0ustar00cedced# This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. from gi.repository import Gio, GLib, GObject, Gtk from tryton.common import common from . import View from .form import ViewForm class ListBoxViewForm(ViewForm): def __init__(self, view_id, screen, xml): self._record = None super().__init__(view_id, screen, xml) self.scroll.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.NEVER) @property def record(self): return self._record @record.setter def record(self, value): self._record = value def button_clicked(self, widget): if self.screen.selected_records == [self.record]: super().button_clicked(widget) class ListBoxItem(GObject.Object): def __init__(self, record): super().__init__() self.record = record class ListBoxModel(GObject.Object, Gio.ListModel): def __init__(self, group): super().__init__() self.group = group self._records = {} def do_get_item(self, position): if position >= len(self.group): return None record = self.group[position] if record.id not in self._records: self._records[record.id] = ListBoxItem(record) return self._records[record.id] def do_get_item_type(self): return ListBoxItem def do_get_n_items(self): return len(self.group) class ViewListForm(View): editable = True xml_parser = None def __init__(self, view_id, screen, xml): super().__init__(view_id, screen, xml) self.creatable = bool(int(self.attributes.get('creatable', 1))) self.view_type = 'list-form' self.form_xml = xml self.listbox = Gtk.ListBox.new() self.listbox.connect('row-selected', self._row_selected) self.listbox.props.activate_on_single_click = False self.listbox.set_selection_mode(Gtk.SelectionMode.MULTIPLE) self.widget = Gtk.ScrolledWindow.new() self.widget.add_with_viewport(self.listbox) self._model = None self._view_forms = [] def display(self): if self._model is None or self._model.group is not self.group: self._view_forms = [] self._model = ListBoxModel(self.group) self.listbox.bind_model(self._model, self._create_form) for idx, view_form in enumerate(self._view_forms): view_form.display() def _create_form(self, item): view_form = ListBoxViewForm(self.view_id, self.screen, self.form_xml) view_form.record = item.record view_form.widget.props.margin = 3 self._view_forms.append(view_form) frame = Gtk.Frame.new() frame.add(view_form.widget) frame.show_all() return frame def set_value(self): for view_form in self._view_forms: view_form.set_value() def get_fields(self): if self._view_forms: return self._view_forms[0].get_fields() return [] def get_buttons(self): if self._view_forms: return self._view_forms[0].get_buttons() return [] def destroy(self): for view_form in self._view_forms: view_form.destroy() self.widget.destroy() @property def selected_records(self): selected_rows = self.listbox.get_selected_rows() return [ self._model.get_item(r.get_index()).record for r in selected_rows] @property def listed_records(self): return list(self._model.group) def group_list_changed(self, group, action, *args): if action == 'record-added': record, position = args self._model.emit('items-changed', position, 0, 1) self._view_forms.insert(position, self._view_forms.pop()) elif action == 'record-removed': record, position = args self._model.emit('items-changed', position, 1, 0) self._view_forms.pop(position) def set_cursor(self, new=False, reset_view=True): for idx, form in enumerate(self._view_forms): if form.record == self.record: self._select_show_row(idx) break def get_selected_paths(self): return [[r.id] for r in self.selected_records] def select_nodes(self, nodes): if not nodes: return nodes = {n[0] for n in nodes} self.listbox.handler_block_by_func(self._row_selected) try: self.listbox.unselect_all() for idx, view_form in enumerate(self._view_forms): if view_form.record.id in nodes: row = self.listbox.get_row_at_index(idx) if not row: continue self.listbox.select_row(row) finally: self.listbox.handler_unblock_by_func(self._row_selected) def _row_selected(self, listbox, row): previous_record = self.record if (previous_record and previous_record not in previous_record.group): previous_record = None if row: self.record = self._model.get_item(row.get_index()).record else: self.record = None def go_previous(): self.record = previous_record self.set_cursor() def save(): if not previous_record.destroyed: if not previous_record.save(): go_previous() def pre_validate(): if not previous_record.destroyed: if not previous_record.pre_validate(): go_previous() if previous_record and previous_record != self.record: if not self.screen.parent: if not previous_record.validate(self.get_fields()): go_previous() return True GLib.idle_add(save) elif self.screen.pre_validate: GLib.idle_add(pre_validate) @common.idle_add def _select_show_row(self, index): # translate_coordinates requires that both widgets are realized if not self.listbox.get_realized(): return # unselect_all triggers a loop in _row_selected if the record is not # valid self.listbox.handler_block_by_func(self._row_selected) try: self.listbox.unselect_all() finally: self.listbox.handler_unblock_by_func(self._row_selected) row = self.listbox.get_row_at_index(index) if not row or not row.get_realized(): return self.listbox.select_row(row) y_position = row.translate_coordinates(self.listbox, 0, 0)[1] y_size = row.get_allocated_height() vadjustment = self.widget.get_vadjustment() vadjustment.clamp_page(y_position, y_position + y_size) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1745680285.2307782 tryton-7.0.24/tryton/gui/window/view_form/view/list_gtk/0000755000175000017500000000000015003173635021477 5ustar00cedced././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685599.0 tryton-7.0.24/tryton/gui/window/view_form/view/list_gtk/__init__.py0000644000175000017500000000022014517761237023614 0ustar00cedced# This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1725720314.0 tryton-7.0.24/tryton/gui/window/view_form/view/list_gtk/editabletree.py0000644000175000017500000003301314667063372024515 0ustar00cedced# This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. import gettext import logging from itertools import chain, cycle, islice from gi.repository import Gdk, GLib, Gtk from tryton.common import MODELACCESS from tryton.common.datetime_ import Date, Time _ = gettext.gettext logger = logging.getLogger(__name__) def focusable_cells(column, editable=True): for cell in column.get_cells(): if (not editable or (isinstance(cell, ( Gtk.CellRendererText, Gtk.CellRendererCombo)) and cell.get_property('editable')) or (isinstance(cell, Gtk.CellRendererToggle) and cell.get_property('activatable'))): yield cell class TreeView(Gtk.TreeView): display_counter = 0 def __init__(self, view): super(TreeView, self).__init__() self.view = view def next_column( self, path, column=None, cell=None, editable=True, _sign=1): if cell: cells = list(focusable_cells(column, editable)) if len(cells) > 1: idx = cells.index(cell) + _sign if 0 <= idx < len(cells): return (column, cells[idx]) columns = self.get_columns() if column is None: column = columns[-1 if _sign > 0 else 0] model = self.get_model() record = model.get_value(model.get_iter(path), 0) if _sign < 0: columns.reverse() current_idx = columns.index(column) + 1 for column in islice(cycle(columns), current_idx, len(columns) + current_idx): if not column.name: continue widget = self.view.get_column_widget(column) try: record.load(column.name, process_exception=False) except Exception: logger.error( f"Error loading '{column.name}' for {record}", exc_info=True) return (None, None) field = record.group.fields[column.name] field.state_set(record, states=('readonly', 'invisible')) invisible = field.get_state_attrs(record).get('invisible', False) if not column.get_visible(): invisible = True if editable: readonly = widget.attrs.get('readonly', field.get_state_attrs(record).get('readonly', False)) else: readonly = False if not (invisible or readonly): cells = list(focusable_cells(column, editable)) if cells: cell = cells[0 if _sign > 0 else -1] else: continue return (column, cell) return (None, None) def prev_column(self, path, column=None, cell=None, editable=True): return self.next_column( path, column=column, cell=cell, editable=editable, _sign=-1) class EditableTreeView(TreeView): leaving_record_events = ( Gdk.KEY_Up, Gdk.KEY_Down, Gdk.KEY_Return) leaving_events = leaving_record_events + ( Gdk.KEY_Tab, Gdk.KEY_ISO_Left_Tab, Gdk.KEY_KP_Enter) def on_quit_cell( self, current_record, column, renderer, value, callback=None): current_record.load(column.name, process_exception=False) field = current_record.group.fields[column.name] widget = self.view.get_column_widget(column) # The value has not changed and is valid ... do nothing. if value == widget.get_textual_value(current_record) \ and field.validate(current_record): if callback: callback() return if widget.renderer != renderer: for widget in chain(widget.prefixes, widget.suffixes): if widget.renderer == renderer: break else: raise ValueError("Unknown renderer") widget.value_from_text(current_record, value, callback=callback) def on_open_remote(self, current_record, column, create, value, entry=None, callback=None): widget = self.view.get_column_widget(column) if value != widget.get_textual_value(current_record) or not value: changed = True else: changed = False try: widget.open_remote(current_record, create, changed, value, callback=callback) except NotImplementedError: pass def on_create_line(self): access = MODELACCESS[self.view.screen.model_name] model = self.get_model() limit = (self.view.screen.size_limit is not None and (len(model) >= self.view.screen.size_limit >= 0)) if not self.view.creatable or not access['create'] or limit: return if self.view.screen.new_position == 0: method = model.prepend else: method = model.append new_record = model.group.new() res = method(new_record) sequence = self.view.attributes.get('sequence') if sequence: model.group.set_sequence( field=sequence, position=self.view.screen.new_position) return res def set_cursor( self, path, focus_column=None, cell=None, start_editing=False): self.grab_focus() if focus_column: widget = self.view.get_column_widget(focus_column) if isinstance(widget.renderer, Gtk.CellRendererToggle): start_editing = False self.scroll_to_cell(path, focus_column, use_align=False) if cell: self.set_cursor_on_cell(path, focus_column, cell, start_editing) else: super(EditableTreeView, self).set_cursor( path, focus_column, start_editing) def set_value(self): path, column = self.get_cursor() if not path or not column or not column.name: return True for renderer in column.get_cells(): if renderer.props.editing: widget = self.view.get_column_widget(column) editable = widget.get_editable(renderer) self.on_editing_done(editable, renderer) return True def on_keypressed(self, entry, event, renderer): path = self.get_cursor()[0] column = self.get_column_from_renderer(renderer) model = self.get_model() record = model.get_value(model.get_iter(path), 0) self.display_counter += 1 # Force a display leaving = False if event.keyval == Gdk.KEY_Right: if isinstance(entry, Gtk.Entry): if entry.get_position() >= \ len(entry.get_text()) \ and not entry.get_selection_bounds(): leaving = True else: leaving = True elif event.keyval == Gdk.KEY_Left: if isinstance(entry, Gtk.Entry): if entry.get_position() <= 0 \ and not entry.get_selection_bounds(): leaving = True else: leaving = True if event.keyval in self.leaving_events or leaving: if isinstance(entry, (Date, Time)): entry.activate() txt = entry.props.value elif isinstance(entry, Gtk.Entry): txt = entry.get_text() elif isinstance(entry, Gtk.ComboBox): active = entry.get_active() if active < 0: txt = None else: model = entry.get_model() index = entry.get_property('entry-text-column') txt = model[active][index] else: return True keyval = event.keyval entry.handler_block(entry.editing_done_id) def callback(): entry.handler_unblock(entry.editing_done_id) if (keyval in [Gdk.KEY_Tab, Gdk.KEY_KP_Enter] or (keyval == Gdk.KEY_Right and leaving)): GLib.idle_add(self.set_cursor, path, *self.next_column(path, column, renderer), True) elif (keyval == Gdk.KEY_ISO_Left_Tab or (keyval == Gdk.KEY_Left and leaving)): GLib.idle_add(self.set_cursor, path, *self.prev_column(path, column, renderer), True) elif keyval in self.leaving_record_events: fields = list(self.view.widgets.keys()) if not record.validate(fields): invalid_fields = record.invalid_fields col = None for col in self.get_columns(): if col.name in invalid_fields: break GLib.idle_add(self.set_cursor, path, col, None, True) return if (( self.view.screen.pre_validate and not record.pre_validate()) or (not self.view.screen.parent and not record.save())): GLib.idle_add( self.set_cursor, path, column, None, True) return entry.handler_block(entry.editing_done_id) if keyval == Gdk.KEY_Up: self._key_up(path, model, column) elif keyval == Gdk.KEY_Down: self._key_down(path, model, column) elif keyval == Gdk.KEY_Return: if self.view.screen.new_position == 0: new_path = self._key_up(path, model) else: new_path = self._key_down(path, model) GLib.idle_add(self.set_cursor, new_path, *self.next_column(new_path), True) entry.handler_unblock(entry.editing_done_id) else: GLib.idle_add(self.set_cursor, path, column, None, True) self.on_quit_cell(record, column, renderer, txt, callback=callback) return True elif event.keyval in [Gdk.KEY_F3, Gdk.KEY_F2]: if isinstance(entry, Gtk.Entry): value = entry.get_text() elif isinstance(entry, Gtk.ComboBox): active = entry.get_active() if active < 0: value = None else: model = entry.get_model() index = entry.get_property('entry-text-column') value = model[active][index] else: return True entry.handler_block(entry.editing_done_id) def callback(): widget = self.view.get_column_widget(column) value = widget.get_textual_value(record) if isinstance(entry, Gtk.Entry): entry.set_text(value) else: entry.set_active_text(value) entry.handler_unblock(entry.editing_done_id) self.on_open_remote(record, column, create=(event.keyval == Gdk.KEY_F3), value=value, callback=callback) else: field = record.group.fields[column.name] if isinstance(entry, Gtk.Entry): entry.set_max_length(int(field.attrs.get('size', 0))) record.modified_fields.setdefault(column.name) return False return True def _key_down(self, path, model, column=None): if path[0] == len(model) - 1 and self.view.screen.new_position == -1: self.on_create_line() new_path = Gtk.TreePath((path[0] + 1) % len(model)) if not column: column, cell = self.next_column(new_path) self.set_cursor(new_path, column, start_editing=True) self.scroll_to_cell(new_path) return new_path def _key_up(self, path, model, column=None): if path[0] == 0 and self.view.screen.new_position == 0: self.on_create_line() new_path = Gtk.TreePath(0) else: new_path = Gtk.TreePath((path[0] - 1) % len(model)) if not column: column, cell = self.next_column(new_path) self.set_cursor(new_path, column, start_editing=True) self.scroll_to_cell(new_path) return new_path def on_editing_done(self, entry, renderer): path = self.get_cursor()[0] if not path: return True column = self.get_column_from_renderer(renderer) model = self.get_model() record = model.get_value(model.get_iter(path), 0) if isinstance(entry, (Date, Time)): entry.activate() text = entry.props.value elif isinstance(entry, Gtk.ComboBox): model = entry.get_model() iter_ = entry.get_active_iter() if iter_: text = model.get_value(iter_, entry.props.entry_text_column) else: text = '' else: text = entry.get_text() self.on_quit_cell(record, column, renderer, text) def get_column_from_renderer(self, renderer): for column in self.get_columns(): for cell in column.get_cells(): if cell == renderer: return column ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1734198766.0 tryton-7.0.24/tryton/gui/window/view_form/view/list_gtk/widget.py0000644000175000017500000014417714727342756023370 0ustar00cedced# This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. import datetime import gettext import logging import os import webbrowser from functools import partial, wraps from weakref import WeakKeyDictionary from gi.repository import Gdk, GLib, Gtk import tryton.common as common from tryton.common import ( data2pixbuf, file_open, file_selection, file_write, get_gdk_backend) from tryton.common.cellrendererbutton import CellRendererButton from tryton.common.cellrendererclickablepixbuf import ( CellRendererClickablePixbuf) from tryton.common.cellrenderercombo import CellRendererCombo from tryton.common.cellrendererfloat import CellRendererFloat from tryton.common.cellrendererinteger import CellRendererInteger from tryton.common.cellrenderertext import ( CellRendererText, CellRendererTextCompletion) from tryton.common.cellrenderertoggle import CellRendererToggle from tryton.common.completion import get_completion, update_completion from tryton.common.datetime_ import ( CellRendererDate, CellRendererTime, date_parse) from tryton.common.domain_parser import quote from tryton.common.selection import ( PopdownMixin, SelectionMixin, selection_shortcuts) from tryton.config import CONFIG from tryton.gui.window.view_form.screen import Screen from tryton.gui.window.win_form import WinForm from tryton.gui.window.win_search import WinSearch _ = gettext.gettext logger = logging.getLogger(__name__) COLORS = {n: v for n, v in zip( ['muted', 'success', 'warning', 'danger'], CONFIG['tree.colors'].split(','))} def send_keys(renderer, editable, position, treeview): editable.connect('key_press_event', treeview.on_keypressed, renderer) editable.editing_done_id = editable.connect('editing_done', treeview.on_editing_done, renderer) if isinstance(editable, Gtk.ComboBox): def changed(combobox): # "changed" signal is also triggered by text editing # so only trigger editing-done if a row is active if combobox.get_active_iter(): treeview.on_editing_done(combobox, renderer) editable.connect('changed', changed) def catch_errors(error_value=_('#ERROR')): def decorator(func): @wraps(func) def wrapper(self, record): if record.exception: return error_value try: return func(self, record) except Exception: logger.error( f"Error calling {func.__name__} for {self} with {record}", exc_info=True) return error_value return wrapper return decorator def realized(func): @wraps(func) def wrapper(self, *args, **kwargs): has_been_realized = _REALIZED.get(self.view.treeview, False) if not has_been_realized: has_been_realized = self.view.treeview.get_realized() if has_been_realized: self.view.treeview.queue_resize() _REALIZED[self.view.treeview] = True else: return return func(self, *args, **kwargs) return wrapper _REALIZED = WeakKeyDictionary() class CellCache(list): methods = ('set_active', 'set_sensitive', 'set_property') def apply(self, cell): for method, args, kwargs in self: getattr(cell, method)(*args, **kwargs) def decorate(self, cell): def decorate(func): @wraps(func) def wrapper(*args, **kwargs): self.append((func.__name__, args, kwargs)) return func(*args, **kwargs) wrapper.previous = func return wrapper for method in self.methods: if getattr(cell, method, None): setattr(cell, method, decorate(getattr(cell, method))) cell.decorated = True return cell def undecorate(self, cell): for method in self.methods: if getattr(cell, method, None): setattr(cell, method, getattr(cell, method).previous) del cell.decorated @classmethod def cache(cls, func): @wraps(func) def wrapper(self, column, cell, store, iter_, user_data=None): if not hasattr(self, 'display_counters'): self.display_counters = {} if not hasattr(self, 'cell_caches'): self.cell_caches = {} record = store.get_value(iter_, 0) counter = self.view.treeview.display_counter if (self.display_counters.get(record.id) != counter): if getattr(cell, 'decorated', None) or record.exception: func(self, column, cell, store, iter_, user_data) else: cache = cls() cache.decorate(cell) func(self, column, cell, store, iter_, user_data) cache.undecorate(cell) self.cell_caches[record.id] = cache self.display_counters[record.id] = counter else: self.cell_caches[record.id].apply(cell) return wrapper class Cell(object): renderer = None setter = None expand = True attrs = None view = None prefixes = [] suffixes = [] def _get_record_field_from_path(self, path, store=None): if not store: store = self.view.treeview.get_model() record = store.get_value(store.get_iter(path), 0) field = record.group.fields[self.attrs['name']] return record, field def _get_record_field_from_iter(self, iter_, store=None): if not store: store = self.view.treeview.get_model() record = store.get_value(iter_, 0) field = record.group.fields[self.attrs['name']] return record, field def _set_visual(self, cell, record): visual = record.expr_eval(self.attrs.get('visual')) if not visual: visual = record.expr_eval(self.view.attributes.get('visual')) background = COLORS.get(visual) if visual != 'muted' else None foreground = COLORS.get(visual) if visual == 'muted' else None cell.set_property('cell-background', background) if isinstance(cell, Gtk.CellRendererText): cell.set_property('foreground', foreground) cell.set_property('foreground-set', bool(foreground)) def set_editable(self): pass class Affix(Cell): expand = False def __init__(self, view, attrs, protocol=None): super(Affix, self).__init__() self.attrs = attrs self.protocol = protocol self.icon = attrs.get('icon') if protocol: self.renderer = CellRendererClickablePixbuf() self.renderer.connect('clicked', self.clicked) if not self.icon: self.icon = 'tryton-public' elif self.icon: self.renderer = Gtk.CellRendererPixbuf() else: self.renderer = Gtk.CellRendererText() self.view = view @realized @CellCache.cache def setter(self, column, cell, store, iter_, user_data=None): record, field = self._get_record_field_from_iter(iter_, store) field = record[self.attrs['name']] field.state_set(record, states=('invisible',)) invisible = field.get_state_attrs(record).get('invisible', False) cell.set_property('visible', not invisible) if self.icon: if self.icon in record.group.fields: value = record[self.icon].get_client(record) or '' else: value = self.icon if self.attrs.get('icon_type') == 'url': pixbuf = common.IconFactory.get_pixbuf_url( value, size_param=self.attrs.get('url_size')) else: pixbuf = common.IconFactory.get_pixbuf( value, Gtk.IconSize.BUTTON) cell.set_property('pixbuf', pixbuf) else: text = self.attrs.get('string', '') if not text: text = field.get_client(record) or '' cell.set_property('text', text) self._set_visual(cell, record) def clicked(self, renderer, path): record, field = self._get_record_field_from_path(path) value = record[self.attrs['name']].get(record) if value: if self.protocol == 'email': value = 'mailto:%s' % value elif self.protocol == 'callto': value = 'callto:%s' % value elif self.protocol == 'sip': value = 'sip:%s' % value webbrowser.open(value, new=2) class Symbol(Cell): expand = False def __init__(self, view, attrs, position): super().__init__() self.attrs = attrs self.symbol = attrs.get('symbol') self.position = position self.renderer = Gtk.CellRendererText() self.renderer.set_property('yalign', 0) self.renderer.set_property('xalign', 0 if position else 1) self.view = view @realized @CellCache.cache def setter(self, column, cell, store, iter_, user_data=None): record, field = self._get_record_field_from_iter(iter_, store) field.state_set(record, states=('invisible',)) invisible = field.get_state_attrs(record).get('invisible', False) if invisible: cell.set_property('text', '') cell.set_property('visible', not invisible) return symbol, position = field.get_symbol(record, self.symbol) if round(position) == self.position: cell.set_property('text', symbol) cell.set_property('visible', True) else: cell.set_property('text', '') cell.set_property('visible', False) self._set_visual(cell, record) class GenericText(Cell): align = 0 editable = None editing = None def __init__(self, view, attrs, renderer=None): super(GenericText, self).__init__() self.attrs = attrs if renderer is None: renderer = CellRendererText self.renderer = renderer() self.renderer.connect('editing-started', self.editing_started) self.renderer.connect_after( 'editing-started', send_keys, view.treeview) self.renderer.set_property('yalign', 0) self.renderer.set_property('xalign', self.align) self.view = view @property def field_name(self): return self.attrs['name'] @property def model_name(self): return self.view.screen.model_name @realized @CellCache.cache def setter(self, column, cell, store, iter_, user_data=None): record, field = self._get_record_field_from_iter(iter_, store) text = self.get_textual_value(record) if isinstance(cell, Gtk.CellRendererToggle): cell.set_active(bool(text)) else: cell.set_sensitive(not (record.deleted or record.removed)) if isinstance(cell, Gtk.CellRendererText): cell.set_property('strikethrough', record.deleted) cell.set_property('text', text) states = ('invisible',) if self.view.editable: states = ('readonly', 'required', 'invisible') field.state_set(record, states=states) invisible = field.get_state_attrs(record).get('invisible', False) cell.set_property('visible', not invisible) # Sometimes, the treeview with fixed height mode computes a too big # height for not visible cell with text # We can force an empty text because not visible cell can not be edited # and so value_from_text is never called. if invisible and not isinstance(cell, CellRendererToggle): cell.set_property('text', '') if self.view.editable: readonly = self.attrs.get('readonly', field.get_state_attrs(record).get('readonly', False)) if invisible: readonly = True if isinstance(cell, CellRendererToggle): cell.set_property('activatable', not readonly) elif isinstance(cell, (Gtk.CellRendererProgress, CellRendererButton, Gtk.CellRendererPixbuf)): pass else: cell.set_property('editable', not readonly) else: if isinstance(cell, CellRendererToggle): cell.set_property('activatable', False) self._set_visual(cell, record) def open_remote(self, record, create, changed=False, text=None, callback=None): raise NotImplementedError @catch_errors() def get_textual_value(self, record): record.load(self.attrs['name'], process_exception=False) return record[self.attrs['name']].get_client(record) def value_from_text(self, record, text, callback=None): field = record[self.attrs['name']] field.set_client(record, text) if callback: callback() def set_editable(self): if not self.editable or not self.editing: return record, field = self.editing self.editable.set_text(self.get_textual_value(record)) def editing_started(self, cell, editable, path): def remove(editable): self.editable = None self.editing = None self.editable = editable self.editing = self._get_record_field_from_path(path) editable.connect('remove-widget', remove) return False def get_editable(self, renderer): if self.renderer == renderer: return self.editable for cell in self.prefixes + self.suffixes: editable = cell.get_editable(renderer) if editable: return editable class Char(GenericText): @realized @CellCache.cache def setter(self, column, cell, store, iter_, user_data=None): super(Char, self).setter(column, cell, store, iter_, user_data) cell.set_property('single-paragraph-mode', True) class Text(GenericText): pass class Int(GenericText): align = 1 def __init__(self, view, attrs, renderer=None): if renderer is None: renderer = CellRendererInteger super(Int, self).__init__(view, attrs, renderer=renderer) self.factor = float(attrs.get('factor', 1)) self.symbol = attrs.get('symbol') self.grouping = bool(int(attrs.get('grouping', 1))) if self.symbol: self.renderer_prefix = Symbol(view, attrs, 0) self.renderer_suffix = Symbol(view, attrs, 1) @property def prefixes(self): if self.symbol: return [self.renderer_prefix] return [] @property def suffixes(self): if self.symbol: return [self.renderer_suffix] return [] @catch_errors() def get_textual_value(self, record): record.load(self.attrs['name'], process_exception=False) return record[self.attrs['name']].get_client( record, factor=self.factor, grouping=self.grouping) def value_from_text(self, record, text, callback=None): field = record[self.attrs['name']] field.set_client(record, text, factor=self.factor) if callback: callback() class Boolean(GenericText): align = 0.5 def __init__(self, view, attrs=None, renderer=None): if renderer is None: renderer = CellRendererToggle super(Boolean, self).__init__(view, attrs, renderer=renderer) self.renderer.connect('toggled', self._sig_toggled) def _sig_toggled(self, renderer, path): record, field = self._get_record_field_from_path(path) if (self.view.record and self.view.record != record and not self.view.record.validate(self.view.get_fields())): renderer.stop_emission_by_name('toggled') return True if not self.attrs.get('readonly', field.get_state_attrs(record).get('readonly', False)): value = record[self.attrs['name']].get_client(record) record[self.attrs['name']].set_client(record, int(not value)) self.view.treeview.set_cursor(path) return True class URL(Char): @realized @CellCache.cache def setter(self, column, cell, store, iter_, user_data=None): super(URL, self).setter(column, cell, store, iter_, user_data) record, field = self._get_record_field_from_iter(iter_, store) field.state_set(record, states=('readonly',)) readonly = field.get_state_attrs(record).get('readonly', False) cell.set_property('visible', not readonly) class Date(GenericText): def __init__(self, view, attrs, renderer=None): if renderer is None: renderer = CellRendererDate super(Date, self).__init__(view, attrs, renderer=renderer) @realized def setter(self, column, cell, store, iter_, user_data=None): record, field = self._get_record_field_from_iter(iter_, store) self.renderer.props.format = self.get_format(record, field) super(Date, self).setter(column, cell, store, iter_, user_data) def get_format(self, record, field): if field and record: return field.date_format(record) else: return '%x' @catch_errors() def get_textual_value(self, record): record.load(self.attrs['name'], process_exception=False) value = record[self.attrs['name']].get_client(record) if value: return value.strftime(self.renderer.props.format) else: return '' def value_from_text(self, record, text, callback=None): if isinstance(text, str): field = record[self.attrs['name']] try: # Use a datetime instance and rely on field to convert to the # proper type text = date_parse(text, self.get_format(record, field)) except (ValueError, OverflowError): text = None return super().value_from_text(record, text, callback=callback) class Time(Date): def __init__(self, view, attrs, renderer=None): if renderer is None: renderer = CellRendererTime super(Time, self).__init__(view, attrs, renderer=renderer) def get_format(self, record, field): if field and record: return field.time_format(record) else: return '%X' @catch_errors() def get_textual_value(self, record): record.load(self.attrs['name'], process_exception=False) value = record[self.attrs['name']].get_client(record) if value is not None: if isinstance(value, datetime.datetime): value = value.time() return value.strftime(self.renderer.props.format) else: return '' def set_editable(self): if not self.editable or not self.editing: return record, field = self.editing self.editable.get_child().set_text(self.get_textual_value(record)) class TimeDelta(GenericText): align = 1 class Float(Int): def __init__(self, view, attrs, renderer=None): if renderer is None: renderer = CellRendererFloat super(Float, self).__init__(view, attrs, renderer=renderer) self.renderer.monetary = attrs.get('monetary', False) @realized def setter(self, column, cell, store, iter_, user_data=None): super(Float, self).setter(column, cell, store, iter_, user_data) record, field = self._get_record_field_from_iter(iter_, store) digits = field.digits(record, factor=self.factor) cell.digits = digits class Binary(GenericText): align = 1 def __init__(self, view, attrs, renderer=None): if renderer is None: renderer = CellRendererText super(Binary, self).__init__(view, attrs, renderer=renderer) self.renderer.set_property('editable', False) self.renderer.set_property('xalign', self.align) self.renderer_save = _BinarySave(self) self.renderer_select = _BinarySelect(self) if self.attrs.get('filename'): self.renderer_open = _BinaryOpen(self) else: self.renderer_open = None @property def prefixes(self): return filter(None, [self.renderer_open]) @property def suffixes(self): return [self.renderer_save, self.renderer_select] @catch_errors() def get_textual_value(self, record): record.load(self.attrs['name'], process_exception=False) field = record[self.attrs['name']] if hasattr(field, 'get_size'): size = field.get_size(record) else: size = len(field.get(record)) return common.humanize(size, 'B') if size else '' def value_from_text(self, record, text, callback=None): if callback: callback() @realized @CellCache.cache def setter(self, column, cell, store, iter_, user_data=None): record, field = self._get_record_field_from_iter(iter_, store) text = self.get_textual_value(record) cell.set_property('text', text) states = ('invisible',) if self.view.editable: states = ('readonly', 'required', 'invisible') field.state_set(record, states=states) invisible = field.get_state_attrs(record).get('invisible', False) cell.set_property('visible', not invisible) self._set_visual(cell, record) def get_data(self, record, field): if hasattr(field, 'get_data'): data = field.get_data(record) else: data = field.get(record) if isinstance(data, str): data = data.encode('utf-8') return data class _BinaryIcon(Cell): expand = False def __init__(self, binary): super().__init__() self.binary = binary self.renderer = CellRendererClickablePixbuf() self.renderer.connect('clicked', self.clicked) @property def attrs(self): return self.binary.attrs @property def view(self): return self.binary.view @catch_errors(False) def _load_data(self, record): record.load(self.attrs['name'], process_exception=False) return True class _BinarySave(_BinaryIcon): icon_name = 'tryton-download' def __init__(self, binary): super().__init__(binary) pixbuf = common.IconFactory.get_pixbuf( self.icon_name, Gtk.IconSize.BUTTON) self.renderer.set_property('pixbuf', pixbuf) @common.idle_add def clicked(self, renderer, path): filename = '' record, field = self._get_record_field_from_path(path) if self.attrs.get('filename'): filename_field = record.group.fields.get(self.attrs['filename']) filename = filename_field.get(record) filename = file_selection(_('Save As...'), filename=filename, action=Gtk.FileChooserAction.SAVE) if filename: with open(filename, 'wb') as fp: fp.write(self.binary.get_data(record, field)) @realized @CellCache.cache def setter(self, column, cell, store, iter_, user_data=None): record, field = self._get_record_field_from_iter(iter_, store) if not self._load_data(record): cell.set_property('visible', False) return if hasattr(field, 'get_size'): size = field.get_size(record) else: size = len(field.get(record)) field.state_set(record, states=['invisible']) invisible = field.get_state_attrs(record).get('invisible', False) cell.set_property('visible', not invisible and size) self._set_visual(cell, record) class _BinarySelect(_BinaryIcon): def clicked(self, renderer, path): record, field = self._get_record_field_from_path(path) if hasattr(field, 'get_size'): size = field.get_size(record) else: size = len(field.get(record)) if self.attrs.get('filename'): filename_field = record.group.fields[self.attrs['filename']] else: filename_field = None if size: if filename_field: filename_field.set_client(record, None) field.set_client(record, None) else: def _select(): filename = file_selection(_('Open...')) if filename: with open(filename, 'rb') as fp: field.set_client(record, fp.read()) if filename_field: filename_field.set_client( record, os.path.basename(filename)) GLib.idle_add(_select) @realized @CellCache.cache def setter(self, column, cell, store, iter_, user_data=None): record, field = self._get_record_field_from_iter(iter_, store) if not self._load_data(record): cell.set_property('visible', False) return if hasattr(field, 'get_size'): size = field.get_size(record) else: size = len(field.get(record)) if size: icon = 'tryton-clear' else: icon = 'tryton-search' pixbuf = common.IconFactory.get_pixbuf(icon, Gtk.IconSize.BUTTON) cell.set_property('pixbuf', pixbuf) field.state_set(record, states=['invisible', 'readonly']) invisible = field.get_state_attrs(record).get('invisible', False) readonly = self.attrs.get('readonly', field.get_state_attrs(record).get('readonly', False)) if readonly or size: cell.set_property('visible', False) else: cell.set_property('visible', not invisible) self._set_visual(cell, record) class _BinaryOpen(_BinarySave): icon_name = 'tryton-open' def clicked(self, renderer, path): record, field = self._get_record_field_from_path(path) filename_field = record.group.fields.get(self.attrs.get('filename')) filename = filename_field.get(record) file_path = file_write(filename, self.binary.get_data(record, field)) root, type_ = os.path.splitext(filename) if type_: type_ = type_[1:] GLib.idle_add(file_open, file_path, type_) @realized @CellCache.cache def setter(self, column, cell, store, iter_, user_data=None): super().setter(column, cell, store, iter_) record, field = self._get_record_field_from_iter(iter_, store) if not self._load_data(record): cell.set_property('visible', False) return filename_field = record.group.fields.get(self.attrs.get('filename')) filename = filename_field.get(record) if not filename: cell.set_property('visible', False) class Image(GenericText): align = 0.5 def __init__(self, view, attrs=None, renderer=None): if renderer is None: renderer = Gtk.CellRendererPixbuf super(Image, self).__init__(view, attrs, renderer) self.height = int(attrs.get('height', 100)) self.width = int(attrs.get('width', 300)) self.renderer.set_fixed_size(self.width, self.height) @realized @CellCache.cache def setter(self, column, cell, store, iter_, user_data=None): record, field = self._get_record_field_from_iter(iter_, store) value = self._get_data(record) if value is None: cell.set_property('pixbuf', None) return pixbuf = data2pixbuf(value) if pixbuf: pixbuf = common.resize_pixbuf(pixbuf, self.width, self.height) cell.set_property('pixbuf', pixbuf) self._set_visual(cell, record) @catch_errors(None) def _get_data(self, record): record.load(self.attrs['name'], process_exception=False) field = record[self.attrs['name']] value = field.get_client(record) if isinstance(value, int): if value > CONFIG['image.max_size']: value = None else: value = field.get_data(record) return value @catch_errors() def get_textual_value(self, record): record.load(self.attrs['name'], process_exception=False) field = record[self.attrs['name']] return str(field.get_size(record)) class M2O(GenericText): def __init__(self, view, attrs, renderer=None): if renderer is None and int(attrs.get('completion', 1)): renderer = partial(CellRendererTextCompletion, self.set_completion) super(M2O, self).__init__(view, attrs, renderer=renderer) def get_model(self, record, field): return self.attrs['relation'] def has_target(self, value): return value is not None def value_from_id(self, model, id_, str_=None): if str_ is None: str_ = '' return id_, str_ def id_from_value(self, value): return value def value_from_text(self, record, text, callback=None): field = record.group.fields[self.attrs['name']] model = self.get_model(record, field) if not text: field.set_client( record, self.value_from_id(model, None, '')) if callback: callback() return if model and common.get_toplevel_window().get_focus(): field = record[self.attrs['name']] win = self.search_remote(record, field, text, callback=callback) win.show() def editing_started(self, cell, editable, path): super(M2O, self).editing_started(cell, editable, path) record, field = self._get_record_field_from_path(path) model = self.get_model(record, field) def changed(editable): text = editable.get_text() if self.get_textual_value(record) != text: field.set_client( record, self.value_from_id(model, None, '')) if self.has_target(field.get(record)): icon1, tooltip1 = 'tryton-open', _("Open the record ") icon2, tooltip2 = 'tryton-clear', _("Clear the field ") else: icon1, tooltip1 = None, '' icon2, tooltip2 = 'tryton-search', _("Search a record ") for pos, icon, tooltip in [ (Gtk.EntryIconPosition.PRIMARY, icon1, tooltip1), (Gtk.EntryIconPosition.SECONDARY, icon2, tooltip2)]: if icon: pixbuf = common.IconFactory.get_pixbuf( icon, Gtk.IconSize.MENU) else: pixbuf = None editable.set_icon_from_pixbuf(pos, pixbuf) editable.set_icon_tooltip_text(pos, tooltip) def icon_press(editable, icon_pos, event): editable.grab_focus() value = field.get(record) if not model: return if (icon_pos == Gtk.EntryIconPosition.SECONDARY and self.has_target(value)): field.set_client( record, self.value_from_id(model, None, '')) editable.set_text('') elif self.has_target(value): self.open_remote(record, create=False, changed=False) else: self.open_remote( record, create=False, changed=True, text=editable.get_text()) editable.connect('icon-press', icon_press) editable.connect('changed', changed) changed(editable) return False def open_remote( self, record, create=True, changed=False, text=None, defaults=None, callback=None): field = record.group.fields[self.attrs['name']] model = self.get_model(record, field) access = common.MODELACCESS[model] if (create and not (int(self.attrs.get('create', 1)) and access['create'])): return elif not access['read']: return domain = field.domain_get(record) context = field.get_context(record) if not create and changed: self.search_remote(record, field, text, callback=callback).show() return target_id = self.id_from_value(field.get(record)) breadcrumb = list(self.view.screen.breadcrumb) breadcrumb.append( field.attrs.get('string') or common.MODELNAME.get(model)) screen = Screen(model, domain=domain, context=context, mode=['form'], view_ids=self.attrs.get('view_ids', '').split(','), exclude_field=field.attrs.get('relation_field'), breadcrumb=breadcrumb) def open_callback(result): if result: value = self.value_from_id( model, screen.current_record.id, screen.current_record.rec_name()) field.set_client(record, value, force_change=True) if callback: callback() if target_id and target_id >= 0: screen.load([target_id]) screen.current_record = screen.group.get(target_id) WinForm(screen, open_callback, save_current=True) else: defaults = defaults.copy() if defaults is not None else {} defaults['rec_name'] = text WinForm( screen, open_callback, new=True, save_current=True, defaults=defaults) def search_remote(self, record, field, text, callback=None): model = self.get_model(record, field) domain = field.domain_get(record) context = field.get_search_context(record) order = field.get_search_order(record) access = common.MODELACCESS[model] create_access = int(self.attrs.get('create', 1)) and access['create'] def search_callback(found): value = None if found: value = self.value_from_id(model, *found[0]) field.set_client(record, value) if callback: callback() win = WinSearch(model, search_callback, sel_multi=False, context=context, domain=domain, order=order, view_ids=self.attrs.get('view_ids', '').split(','), new=create_access, title=self.attrs.get('string')) win.screen.search_filter(quote(text)) return win def set_completion(self, entry, path): record, field = self._get_record_field_from_path(path) if entry.get_completion(): entry.set_completion(None) model = self.get_model(record, field) if not model: return access = common.MODELACCESS[model] completion = get_completion( search=access['read'], create=int(self.attrs.get('create', 1)) and access['create']) completion.connect('match-selected', self._completion_match_selected, record, field, model) completion.connect('action-activated', self._completion_action_activated, record, field) entry.set_completion(completion) entry.connect('key-press-event', self._key_press, record, field) entry.connect('changed', self._update_completion, record, field) def _key_press(self, entry, event, record, field): if (self.has_target(field.get(record)) and event.keyval in [Gdk.KEY_Delete, Gdk.KEY_BackSpace]): entry.set_text('') return False def _completion_match_selected( self, completion, model, iter_, record, field, model_name): rec_name, record_id, defaults = model.get(iter_, 0, 1, 2) if record_id is not None: field.set_client( record, self.value_from_id(model_name, record_id, rec_name)) completion.get_entry().set_text(rec_name) completion_model = completion.get_model() completion_model.clear() completion_model.search_text = rec_name else: entry = completion.get_entry() entry.handler_block(entry.editing_done_id) def callback(): entry.handler_unblock(entry.editing_done_id) entry.set_text(field.get_client(record)) self.open_remote( record, create=True, text=entry.get_text(), defaults=defaults, callback=callback) return True def _update_completion(self, entry, record, field): value = field.get(record) if self.has_target(value): id_ = self.id_from_value(value) if id_ is not None and id_ >= 0: return model = self.get_model(record, field) update_completion(entry, record, field, model) def _completion_action_activated(self, completion, index, record, field): entry = completion.get_entry() entry.handler_block(entry.editing_done_id) def callback(): entry.handler_unblock(entry.editing_done_id) entry.set_text(field.get_client(record)) if index == 0: self.open_remote(record, create=False, changed=True, text=entry.get_text(), callback=callback) elif index == 1: self.open_remote(record, create=True, text=entry.get_text(), callback=callback) else: entry.handler_unblock(entry.editing_done_id) class O2O(M2O): pass class O2M(GenericText): align = 0.5 @catch_errors() def get_textual_value(self, record): record.load(self.attrs['name'], process_exception=False) field = record[self.attrs['name']] return '( ' + str(len(field.get_eval(record))) + ' )' def value_from_text(self, record, text, callback=None): if callback: callback() def open_remote(self, record, create=True, changed=False, text=None, callback=None): group = record.value[self.attrs['name']] field = record.group.fields[self.attrs['name']] relation = field.attrs['relation'] context = field.get_context(record) access = common.MODELACCESS[relation] if not access['read']: return breadcrumb = list(self.view.screen.breadcrumb) breadcrumb.append( field.attrs.get('string') or common.MODELNAME.get(relation)) screen = Screen(relation, mode=['tree', 'form'], view_ids=self.attrs.get('view_ids', '').split(','), exclude_field=field.attrs.get('relation_field'), limit=None, context=self.view.screen.context, breadcrumb=breadcrumb) screen.pre_validate = bool(int(self.attrs.get('pre_validate', 0))) screen.group = group def open_callback(result): if callback: callback() WinForm(screen, open_callback, view_type='tree', context=context) class M2M(O2M): def open_remote(self, record, create=True, changed=False, text=None, callback=None): group = record.value[self.attrs['name']] field = record.group.fields[self.attrs['name']] relation = field.attrs['relation'] context = field.get_context(record) domain = field.domain_get(record) breadcrumb = list(self.view.screen.breadcrumb) breadcrumb.append( field.attrs.get('string') or common.MODELNAME.get(relation)) screen = Screen(relation, mode=['tree', 'form'], view_ids=self.attrs.get('view_ids', '').split(','), exclude_field=field.attrs.get('relation_field'), limit=None, context=self.view.screen.context, breadcrumb=breadcrumb) screen.group = group def open_callback(result): if callback: callback() WinForm(screen, open_callback, view_type='tree', domain=domain, context=context) class Selection(GenericText, SelectionMixin, PopdownMixin): def __init__(self, *args, **kwargs): if 'renderer' not in kwargs: kwargs['renderer'] = CellRendererCombo super(Selection, self).__init__(*args, **kwargs) if self.view and self.view.editable: self.init_selection() # Use a variable let Python holding reference when calling # set_property model = self.get_popdown_model(self.selection)[0] self.renderer.set_property('model', model) self.renderer.set_property('text-column', 0) def get_value(self, record, field): return field.get(record) @catch_errors() def get_textual_value(self, record): related = self.attrs['name'] + ':string' if not self.view.editable and related in record.value: return record.value[related] record.load(self.attrs['name'], process_exception=False) field = record[self.attrs['name']] self.update_selection(record, field) value = self.get_value(record, field) text = dict(self.selection).get(value, '') if value and not text: text = self.get_inactive_selection(value) return text def value_from_text(self, record, text, callback=None): if callback: callback() def set_editable(self): if not self.editable and not self.editing: return record, field = self.editing value = self.get_value(record, field) self.update_selection(record, field) self.set_popdown_value(self.editable, value) def editing_started(self, cell, editable, path): super(Selection, self).editing_started(cell, editable, path) record, field = self._get_record_field_from_path(path) gdk_backend = get_gdk_backend() if gdk_backend == 'x11': # Combobox does not emit remove-widget when focus is changed self.editable.connect( 'editing-done', lambda *a: self.editable.emit('remove-widget')) selection_shortcuts(editable) def set_value(*a): return self.set_value(editable, record, field) editable.get_child().connect('activate', set_value) editable.get_child().connect('focus-out-event', set_value) if gdk_backend != 'x11': # For non X11 backend we can rely the focus-out-event to be sent # correctly def remove_entry(entry, event): editable.emit('editing-done') editable.emit('remove-widget') editable.get_child().connect('focus-out-event', remove_entry) editable.connect('changed', set_value) self.update_selection(record, field) self.set_popdown(self.selection, editable) value = self.get_value(record, field) if not self.set_popdown_value(editable, value): self.get_inactive_selection(value) self.set_popdown_value(editable, value) return False def set_value(self, editable, record, field): value = self.get_popdown_value(editable) if 'relation' in self.attrs and value: active = editable.get_active() if active < 0: text = None else: model = editable.get_model() index = editable.get_property('entry-text-column') text = model[active][index] value = (value, text) field.set_client(record, value) return False class MultiSelection(GenericText, SelectionMixin): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) if self.view and self.view.editable: self.init_selection() @realized @CellCache.cache def setter(self, column, cell, store, iter_, user_data=None): super().setter(column, cell, store, iter_, user_data=user_data) cell.set_property('editable', False) def value_from_text(self, record, text, callback=None): if callback: callback() @catch_errors() def get_textual_value(self, record): related = self.attrs['name'] + ':string' if not self.view.editable and related in record.value: return ";".join(record.value[related]) record.load(self.attrs['name'], process_exception=False) field = record[self.attrs['name']] self.update_selection(record, field) selection = dict(self.selection) values = [] for value in field.get_eval(record): text = selection.get(value, '') values.append(text) return ';'.join(values) class Reference(M2O): def __init__(self, view, attrs, renderer=None): super(Reference, self).__init__(view, attrs, renderer=renderer) self.renderer_selection = _ReferenceSelection(view, attrs) @property def prefixes(self): return [self.renderer_selection] def get_model(self, record, field): value = field.get_client(record) if value: model, value = value return model def has_target(self, value): if not value: return False model, value = value.split(',') if not value: value = None else: try: value = int(value) except ValueError: value = None return model and value >= 0 def value_from_id(self, model, id_, str_=None): if str_ is None: str_ = '' return model, (id_, str_) def id_from_value(self, value): _, value = value.split(',') return int(value) def get_textual_value(self, record): value = super().get_textual_value(record) if isinstance(value, tuple): model, value = value return value class _ReferenceSelection(Selection): def get_value(self, record, field): value = field.get_client(record) if value: model, value = value else: model = None return model def set_value(self, editable, record, field): value = self.get_popdown_value(editable) if value != self.get_value(record, field): if value: value = (value, (-1, '')) else: value = ('', '') field.set_client(record, value) return False class Dict(GenericText): align = 0.5 def __init__(self, view, attrs): super().__init__(view, attrs) self.renderer.props.editable = False def setter(self, column, cell, store, iter_, user_data=None): super().setter(column, cell, store, iter_, user_data=None) cell.props.editable = False @catch_errors() def get_textual_value(self, record): record.load(self.attrs['name'], process_exception=False) field = record[self.attrs['name']] return '(%s)' % len(field.get_client(record)) class ProgressBar(Cell): align = 0.5 orientations = { 'left_to_right': (Gtk.Orientation.HORIZONTAL, False), 'right_to_left': (Gtk.Orientation.HORIZONTAL, True), 'bottom_to_top': (Gtk.Orientation.VERTICAL, True), 'top_to_bottom': (Gtk.Orientation.VERTICAL, False), } def __init__(self, view, attrs): super(ProgressBar, self).__init__() self.view = view self.attrs = attrs self.renderer = Gtk.CellRendererProgress() orientation, inverted = self.orientations.get( self.attrs.get('orientation', 'left_to_right')) self.renderer.set_orientation(orientation) self.renderer.set_property('inverted', inverted) self.renderer.set_property('yalign', 0) @realized @CellCache.cache def setter(self, column, cell, store, iter_, user_data=None): record, field = self._get_record_field_from_iter(iter_, store) field.state_set(record, states=('invisible',)) invisible = field.get_state_attrs(record).get('invisible', False) cell.set_property('visible', not invisible) text = self.get_textual_value(record) if text: text = _('%s%%') % text cell.set_property('text', text) value = field.get(record) or 0.0 cell.set_property('value', value * 100) self._set_visual(cell, record) def open_remote(self, record, create, changed=False, text=None, callback=None): raise NotImplementedError @catch_errors() def get_textual_value(self, record): record.load(self.attrs['name'], process_exception=False) field = record[self.attrs['name']] return field.get_client(record, factor=100) or '' def value_from_text(self, record, text, callback=None): field = record[self.attrs['name']] field.set_client(record, float(text)) if callback: callback() class Button(Cell): def __init__(self, view, attrs): super(Button, self).__init__() self.attrs = attrs self.renderer = CellRendererButton(attrs.get('string', _('Unknown'))) self.view = view self.renderer.connect('clicked', self.button_clicked) self.renderer.set_property('yalign', 0) @realized @CellCache.cache def setter(self, column, cell, store, iter_, user_data=None): record = store.get_value(iter_, 0) states = record.expr_eval(self.attrs.get('states', {})) invisible = states.get('invisible', False) cell.set_property('visible', not invisible) readonly = states.get('readonly', False) cell.set_property('sensitive', not readonly) if self.attrs.get('type', 'class') == 'class': parent = record.parent if record else None while parent: if parent.modified: cell.set_property('sensitive', False) break parent = parent.parent # TODO icon self._set_visual(cell, record) @common.idle_add def button_clicked(self, widget, path): if not path: return True store = self.view.treeview.get_model() record = store.get_value(store.get_iter(path), 0) if self.view.record and self.view.record != record: widget.stop_emission_by_name('clicked') return True state_changes = record.expr_eval( self.attrs.get('states', {})) if state_changes.get('invisible') \ or state_changes.get('readonly'): return True self.view.treeview.set_cursor(path) widget.handler_block_by_func(self.button_clicked) try: self.view.screen.button(self.attrs) finally: widget.handler_unblock_by_func(self.button_clicked) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1735581347.0 tryton-7.0.24/tryton/gui/window/view_form/view/screen_container.py0000644000175000017500000006232414734557243023574 0ustar00cedced# This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. import datetime import gettext from gi.repository import Gdk, GLib, GObject, Gtk import tryton.common as common from tryton.common.datetime_ import Date, DateTime, Time, add_operators from tryton.common.domain_parser import quote from tryton.common.number_entry import NumberEntry from tryton.pyson import PYSONDecoder _ = gettext.gettext class Between(Gtk.HBox): _changed_signal = None def __init__(self, _entry=Gtk.Entry): super().__init__() self.from_ = _entry() self.pack_start(self.from_, expand=True, fill=True, padding=0) self.pack_start( Gtk.Label(label=_('..')), expand=False, fill=False, padding=0) self.to = _entry() self.pack_start(self.to, expand=True, fill=True, padding=0) if self._changed_signal: self.from_.connect_after( self._changed_signal, self._from_changed) @property def _connect_widgets(self): return [self.from_, self.to] def connect(self, signal, callback): for widget in self._connect_widgets: try: widget.connect(signal, callback) except TypeError: pass def get_value(self): from_ = self._get_value(self.from_) to = self._get_value(self.to) if from_ and to: if from_ != to: return '%s..%s' % (quote(from_), quote(to)) else: return quote(from_) elif from_: return '>=%s' % quote(from_) elif to: return '<=%s' % quote(to) def _get_value(self, entry): raise NotImplementedError def set_value(self, from_, to): self._set_value(self.from_, from_) self._set_value(self.to, to) def _set_value(self, entry, value): raise NotImplementedError def _from_changed(self, widget): self._set_value(self.to, self._get_value(self.from_)) class WithOperators: def __init__(self, entry): self.entry = entry def __call__(self): return add_operators(self.entry()) class BetweenDates(Between): _entry = None def __init__(self, format_=None): super().__init__(WithOperators(self._entry)) if format_: self.from_.props.format = format_ self.to.props.format = format_ def _set_value(self, entry, value): entry.props.value = value class Dates(BetweenDates): _entry = Date _changed_signal = 'date-changed' def _get_value(self, widget): value = widget.props.value if value: return value.strftime(widget.props.format) class Times(BetweenDates): _entry = Time _changed_signal = 'time-changed' @property def _connect_widgets(self): return [self.from_.get_child(), self.to.get_child()] def _get_value(self, widget): value = widget.props.value if value: return datetime.time.strftime(value, widget.props.format) class DateTimes(BetweenDates): _entry = DateTime _changed_signal = 'datetime-changed' def __init__(self, date_format, time_format): super().__init__() self.from_.props.date_format = date_format self.to.props.date_format = date_format self.from_.props.time_format = time_format self.to.props.time_format = time_format @property def _connect_widgets(self): return self.from_.get_children() + self.to.get_children() def _get_value(self, widget): value = widget.props.value if value: return value.strftime( widget.props.date_format + ' ' + widget.props.time_format) class Numbers(Between): _changed_signal = 'changed' def __init__(self): super().__init__(NumberEntry) def _get_value(self, widget): return widget.get_text() def _set_value(self, entry, value): entry.set_text(value or '') class Selection(Gtk.ScrolledWindow): def __init__(self, selections): super(Selection, self).__init__() self.treeview = Gtk.TreeView() model = Gtk.ListStore(GObject.TYPE_STRING) for selection in selections: model.append((selection,)) self.treeview.set_model(model) column = Gtk.TreeViewColumn() cell = Gtk.CellRendererText() column.pack_start(cell, expand=True) column.add_attribute(cell, 'text', 0) self.treeview.append_column(column) self.treeview.get_selection().set_mode(Gtk.SelectionMode.MULTIPLE) self.treeview.set_headers_visible(False) self.add(self.treeview) self.set_policy( Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC) self.set_shadow_type(Gtk.ShadowType.ETCHED_IN) self.set_min_content_height(min(20 * len(selections), 200)) self.set_max_content_height(200) def get_value(self): values = [] model, paths = self.treeview.get_selection().get_selected_rows() if not paths: return for path in paths: iter_ = model.get_iter(path) values.append(model.get_value(iter_, 0)) return ';'.join(quote(v) for v in values) class ScreenContainer(object): def __init__(self, screen, tab_domain): self.screen = screen self.viewport = Gtk.Viewport() self.viewport.set_shadow_type(Gtk.ShadowType.NONE) self.vbox = Gtk.VBox(spacing=3) self.alternate_viewport = Gtk.Viewport() self.alternate_viewport.set_shadow_type(Gtk.ShadowType.NONE) self.alternate_view = False self.search_popover = None self.search_grid = None self.last_search_text = '' self.tab_domain = tab_domain or [] self.tab_counter = [] tooltips = common.Tooltips() self.filter_vbox = Gtk.VBox(spacing=0) self.filter_vbox.set_border_width(0) hbox = Gtk.HBox(homogeneous=False, spacing=0) self.search_entry = Gtk.Entry() self.search_entry.set_placeholder_text(_('Search')) self.search_entry.set_alignment(0.0) self.search_entry.set_icon_from_pixbuf( Gtk.EntryIconPosition.PRIMARY, common.IconFactory.get_pixbuf('tryton-filter', Gtk.IconSize.MENU)) self.search_entry.set_icon_tooltip_text( Gtk.EntryIconPosition.PRIMARY, _('Open filters')) self.completion = Gtk.EntryCompletion() self.completion.set_model(Gtk.ListStore(str)) self.completion.set_text_column(0) self.completion.props.inline_selection = True self.completion.props.popup_set_width = False self.completion.set_match_func(lambda *a: True) self.completion.connect('match-selected', self.match_selected) self.search_entry.connect('activate', self.activate) self.search_entry.set_completion(self.completion) self.search_entry.connect('key-press-event', self.key_press) self.search_entry.connect('focus-in-event', self.focus_in) self.search_entry.connect('icon-press', self.icon_press) hbox.pack_start(self.search_entry, expand=True, fill=True, padding=0) def popup(widget): menu = widget._menu for child in menu.get_children(): menu.remove(child) if not widget.props.active: menu.popdown() return def menu_position(menu, data=None): widget_allocation = widget.get_allocation() x, y = widget.get_window().get_root_coords( widget_allocation.x, widget_allocation.y) return (x, y + widget_allocation.height, False) for id_, name, domain, access in self.bookmarks(): menuitem = Gtk.MenuItem(label=name) menuitem.connect('activate', self.bookmark_activate, domain) menu.add(menuitem) menu.show_all() if hasattr(menu, 'popup_at_widget'): menu.popup_at_widget( widget, Gdk.Gravity.SOUTH_WEST, Gdk.Gravity.NORTH_WEST, Gtk.get_current_event()) else: menu.popup(None, None, menu_position, 0, 0) def deactivate(menuitem, togglebutton): togglebutton.props.active = False but_bookmark = Gtk.ToggleButton() self.but_bookmark = but_bookmark tooltips.set_tip(but_bookmark, _('Show bookmarks of filters')) but_bookmark.add(common.IconFactory.get_image( 'tryton-bookmarks', Gtk.IconSize.SMALL_TOOLBAR)) but_bookmark.set_relief(Gtk.ReliefStyle.NONE) menu = Gtk.Menu() menu.set_property('reserve-toggle-size', False) menu.connect('deactivate', deactivate, but_bookmark) but_bookmark._menu = menu but_bookmark.connect('toggled', popup) hbox.pack_start(but_bookmark, expand=False, fill=False, padding=0) but_active = Gtk.ToggleButton() self.but_active = but_active self._set_active_tooltip() but_active.add(common.IconFactory.get_image( 'tryton-archive', Gtk.IconSize.SMALL_TOOLBAR)) but_active.set_relief(Gtk.ReliefStyle.NONE) but_active.connect('toggled', self.search_active) hbox.pack_start(but_active, expand=False, fill=False, padding=0) but_prev = Gtk.Button() self.but_prev = but_prev tooltips.set_tip(but_prev, _('Previous')) but_prev.connect('clicked', self.search_prev) but_prev.add(common.IconFactory.get_image( 'tryton-back', Gtk.IconSize.SMALL_TOOLBAR)) but_prev.set_relief(Gtk.ReliefStyle.NONE) hbox.pack_start(but_prev, expand=False, fill=False, padding=0) but_next = Gtk.Button() self.but_next = but_next tooltips.set_tip(but_next, _('Next')) but_next.connect('clicked', self.search_next) but_next.add(common.IconFactory.get_image( 'tryton-forward', Gtk.IconSize.SMALL_TOOLBAR)) but_next.set_relief(Gtk.ReliefStyle.NONE) hbox.pack_start(but_next, expand=False, fill=False, padding=0) hbox.show_all() self.filter_vbox.pack_start(hbox, expand=False, fill=False, padding=0) hseparator = Gtk.HSeparator() hseparator.show() self.filter_vbox.pack_start( hseparator, expand=False, fill=False, padding=0) if self.tab_domain: self.notebook = Gtk.Notebook() try: self.notebook.props.homogeneous = True except AttributeError: # No more supported by GTK+3 pass self.notebook.set_scrollable(True) for name, domain, count in self.tab_domain: hbox = Gtk.HBox(spacing=3) label = Gtk.Label(label='_' + name) label.set_use_underline(True) hbox.pack_start(label, expand=True, fill=True, padding=0) counter = Gtk.Label() hbox.pack_start(counter, expand=False, fill=True, padding=0) hbox.show_all() self.notebook.append_page(Gtk.VBox(), hbox) self.tab_counter.append(counter) self.filter_vbox.pack_start( self.notebook, expand=True, fill=True, padding=0) self.notebook.show_all() # Set the current page before connecting to switch-page to not # trigger the search a second times. self.notebook.set_current_page(0) self.notebook.get_nth_page(0).pack_end( self.viewport, expand=True, fill=True, padding=0) self.notebook.connect('switch-page', self.switch_page) self.notebook.connect_after('switch-page', self.switch_page_after) filter_expand = True else: self.notebook = None self.vbox.pack_end( self.viewport, expand=True, fill=True, padding=0) filter_expand = False self.vbox.pack_start( self.filter_vbox, expand=filter_expand, fill=True, padding=0) self.but_next.set_sensitive(False) self.but_prev.set_sensitive(False) tooltips.enable() def widget_get(self): return self.vbox def show_filter(self): self.but_bookmark.set_sensitive(bool(list(self.bookmarks()))) self.bookmark_match() if self.filter_vbox: self.filter_vbox.show() if self.notebook: self.notebook.set_show_tabs(True) if self.viewport in self.vbox.get_children(): self.vbox.remove(self.viewport) self.notebook.get_nth_page(self.notebook.get_current_page() ).pack_end( self.viewport, expand=True, fill=True, padding=0) def hide_filter(self): if self.filter_vbox: self.filter_vbox.hide() if self.notebook: self.notebook.set_show_tabs(False) if self.viewport not in self.vbox.get_children(): self.notebook.get_nth_page(self.notebook.get_current_page() ).remove(self.viewport) self.vbox.pack_end( self.viewport, expand=True, fill=True, padding=0) def set(self, widget): viewport1 = self.viewport viewport2 = self.alternate_viewport if self.alternate_view: viewport1, viewport2 = viewport2, viewport1 if viewport1.get_child(): viewport1.remove(viewport1.get_child()) if widget == viewport2.get_child(): viewport2.remove(widget) viewport1.add(widget) viewport1.show_all() def update(self): res = self.screen.search_complete(self.get_text()) model = self.completion.get_model() model.clear() for r in res: model.append([r.strip()]) def get_text(self): return self.search_entry.get_text() def set_text(self, value): self.search_entry.set_text(value) self.bookmark_match() def bookmarks(self): for id_, name, domain, access in common.VIEW_SEARCH[ self.screen.model_name]: if self.screen.domain_parser.stringable(domain): yield id_, name, domain, access def bookmark_activate(self, menuitem, domain): self.set_text(self.screen.domain_parser.string(domain)) self.do_search() def bookmark_match(self): current_text = self.get_text() if current_text: current_domain = self.screen.domain_parser.parse(current_text) for id_, name, domain, access in self.bookmarks(): text = self.screen.domain_parser.string(domain) domain = self.screen.domain_parser.parse(text) if (text == current_text or domain == current_domain): self.search_entry.set_icon_from_pixbuf( Gtk.EntryIconPosition.SECONDARY, common.IconFactory.get_pixbuf( 'tryton-bookmark', Gtk.IconSize.MENU)) self.search_entry.set_icon_tooltip_text( Gtk.EntryIconPosition.SECONDARY, _('Remove this bookmark')) self.search_entry.set_icon_activatable( Gtk.EntryIconPosition.SECONDARY, access) self.search_entry.set_icon_sensitive( Gtk.EntryIconPosition.SECONDARY, access) return id_ self.search_entry.set_icon_activatable( Gtk.EntryIconPosition.SECONDARY, bool(current_text)) self.search_entry.set_icon_sensitive( Gtk.EntryIconPosition.SECONDARY, bool(current_text)) self.search_entry.set_icon_from_pixbuf( Gtk.EntryIconPosition.SECONDARY, common.IconFactory.get_pixbuf( 'tryton-bookmark-border', Gtk.IconSize.MENU)) if current_text: self.search_entry.set_icon_tooltip_text( Gtk.EntryIconPosition.SECONDARY, _('Bookmark this filter')) elif self.search_entry.get_icon_tooltip_text( Gtk.EntryIconPosition.SECONDARY): self.search_entry.set_icon_tooltip_text( Gtk.EntryIconPosition.SECONDARY, None) def search_next(self, widget=None): self.screen.search_next(self.get_text()) def search_prev(self, widget=None): self.screen.search_prev(self.get_text()) def search_active(self, widget=None): self._set_active_tooltip() self.screen.search_filter(self.get_text()) def _set_active_tooltip(self): if self.but_active.get_active(): tooltip = _('Show active records') else: tooltip = _('Show inactive records') tooltips = common.Tooltips() tooltips.set_tip(self.but_active, tooltip) def switch_page(self, notebook, page, page_num): current_page = notebook.get_nth_page(notebook.get_current_page()) current_page.remove(self.viewport) new_page = notebook.get_nth_page(page_num) new_page.pack_end(self.viewport, expand=True, fill=True, padding=0) def switch_page_after(self, notebook, page, page_num): self.do_search() notebook.grab_focus() self.screen.count_tab_domain(True) def get_tab_index(self): if not self.notebook: return -1 return self.notebook.get_current_page() def get_tab_domain(self): idx = self.get_tab_index() if idx < 0: return [] return self.tab_domain[idx][1] def set_tab_counter(self, count, idx=None): if not self.tab_counter or not self.notebook: return if idx is None: idx = self.notebook.get_current_page() if idx < 0: return label = self.tab_counter[idx] tooltip = common.Tooltips() if count is None: label.set_label('') tooltip.set_tip(label, '') else: tip = common.humanize(count) if count >= 1000: tip += '+' tooltip.set_tip(label, tip) fmt = '(%d)' if count > 99: fmt = '(%d+)' count = 99 label.set_label(fmt % count) def match_selected(self, completion, model, iter): def callback(): if not self.search_entry.props.window: return self.update() self.search_entry.emit('changed') GLib.idle_add(callback) def activate(self, widget): if not self.search_entry.get_selection_bounds(): self.do_search(widget) def do_search(self, widget=None): self.screen.search_filter(self.get_text()) def set_cursor(self, new=False, reset_view=True): if self.filter_vbox: self.search_entry.grab_focus() def key_press(self, widget, event): def keypress(): if not self.search_entry.props.window: return self.update() self.bookmark_match() GLib.idle_add(keypress) def icon_press(self, widget, icon_pos, event): widget.grab_focus() if icon_pos == Gtk.EntryIconPosition.PRIMARY: self.search_box(widget) elif icon_pos == Gtk.EntryIconPosition.SECONDARY: model_name = self.screen.model_name id_ = self.bookmark_match() if not id_: text = self.get_text() if not text: return name = common.ask(_('Bookmark Name:')) if not name: return domain = self.screen.domain_parser.parse(text) common.VIEW_SEARCH.add(model_name, name, domain) self.set_text(self.screen.domain_parser.string(domain)) else: common.VIEW_SEARCH.remove(model_name, id_) # Refresh icon and bookmark button self.bookmark_match() self.but_bookmark.set_sensitive(bool(list(self.bookmarks()))) def focus_in(self, widget, event): self.update() self.search_entry.emit('changed') def search_box(self, widget): def window_hide(window, *args): window.hide() self.search_entry.grab_focus() def search(): self.search_popover.popdown() text = '' for label, entry in self.search_grid.fields: if isinstance(entry, Gtk.ComboBoxText): value = quote(entry.get_active_text()) or None elif isinstance(entry, (Between, Selection)): value = entry.get_value() else: value = quote(entry.get_text()) or None if value is not None: text += quote(label) + ': ' + value + ' ' self.set_text(text) self.do_search() # Store text after doing the search # because domain parser could simplify the text self.last_search_text = self.get_text() if not self.search_popover: self.search_popover = Gtk.Popover() self.search_popover.set_relative_to(widget) vbox = Gtk.VBox() fields = [f for f in self.screen.domain_parser.fields.values() if f.get('searchable', True) and '.' not in f['name']] self.search_grid = Gtk.Grid(column_spacing=3, row_spacing=3) # Fill table with fields self.search_grid.fields = [] for i, field in enumerate(fields): label = Gtk.Label( label=field['string'], halign=Gtk.Align.START, valign=Gtk.Align.START) self.search_grid.attach(label, 0, i, 1, 1) if field['type'] == 'boolean': entry = Gtk.ComboBoxText() entry.append_text('') selections = (_('True'), _('False')) for selection in selections: entry.append_text(selection) elif field['type'] in ['selection', 'multiselection']: selections = tuple(x[1] for x in field['selection']) entry = Selection(selections) entry.set_vexpand(True) elif field['type'] in ('date', 'datetime', 'time'): date_format = common.date_format( self.screen.context.get('date_format')) if field['type'] == 'date': entry = Dates(date_format) elif field['type'] in ('datetime', 'time'): time_format = PYSONDecoder({}).decode(field['format']) if field['type'] == 'time': entry = Times(time_format) elif field['type'] == 'datetime': entry = DateTimes(date_format, time_format) entry.connect('activate', lambda *a: search()) elif field['type'] in ['integer', 'float', 'numeric']: entry = Numbers() entry.connect('activate', lambda *a: search()) else: entry = Gtk.Entry() entry.connect('activate', lambda *a: search()) label.set_mnemonic_widget(entry) self.search_grid.attach(entry, 1, i, 1, 1) self.search_grid.fields.append((field['string'], entry)) scrolled = Gtk.ScrolledWindow() scrolled.add(self.search_grid) scrolled.set_shadow_type(Gtk.ShadowType.NONE) scrolled.set_policy( Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC) vbox.pack_start(scrolled, expand=True, fill=True, padding=0) find_button = Gtk.Button(label=_('Find')) find_button.connect('clicked', lambda *a: search()) find_button.set_image(common.IconFactory.get_image( 'tryton-search', Gtk.IconSize.SMALL_TOOLBAR)) find_button.set_can_default(True) self.search_popover.set_default_widget(find_button) hbuttonbox = Gtk.HButtonBox() hbuttonbox.pack_start( find_button, expand=False, fill=False, padding=0) hbuttonbox.set_layout(Gtk.ButtonBoxStyle.END) vbox.pack_start(hbuttonbox, expand=False, fill=True, padding=0) self.search_popover.add(vbox) vbox.show_all() scrolled.set_size_request( -1, min(self.search_grid.get_preferred_height()[1], 400)) self.search_popover.set_pointing_to( widget.get_icon_area(Gtk.EntryIconPosition.PRIMARY)) self.search_popover.popup() if self.search_grid.fields: self.search_grid.fields[0][1].grab_focus() if self.last_search_text.strip() != self.get_text().strip(): for label, entry in self.search_grid.fields: if isinstance(entry, Gtk.ComboBoxText): entry.set_active(-1) elif isinstance(entry, Between): entry.set_value(None, None) elif isinstance(entry, Selection): entry.treeview.get_selection().unselect_all() else: entry.set_text('') ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685599.0 tryton-7.0.24/tryton/gui/window/win_csv.py0000644000175000017500000003231114517761237016744 0ustar00cedced# This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. import gettext import locale import os import sys from gi.repository import Gdk, GObject, Gtk from tryton.common import IconFactory from tryton.common.underline import set_underline from tryton.config import TRYTON_ICON from tryton.gui import Main from tryton.gui.window.nomodal import NoModal _ = gettext.gettext encodings = ["ascii", "big5", "big5hkscs", "cp037", "cp424", "cp437", "cp500", "cp720", "cp737", "cp775", "cp850", "cp852", "cp855", "cp856", "cp857", "cp858", "cp860", "cp861", "cp862", "cp863", "cp864", "cp865", "cp866", "cp869", "cp874", "cp875", "cp932", "cp949", "cp950", "cp1006", "cp1026", "cp1140", "cp1250", "cp1251", "cp1252", "cp1253", "cp1254", "cp1255", "cp1256", "cp1257", "cp1258", "euc_jp", "euc_jis_2004", "euc_jisx0213", "euc_kr", "gb2312", "gbk", "gb18030", "hz", "iso2022_jp", "iso2022_jp_1", "iso2022_jp_2", "iso2022_jp_2004", "iso2022_jp_3", "iso2022_jp_ext", "iso2022_kr", "latin_1", "iso8859_2", "iso8859_3", "iso8859_4", "iso8859_5", "iso8859_6", "iso8859_7", "iso8859_8", "iso8859_9", "iso8859_10", "iso8859_13", "iso8859_14", "iso8859_15", "iso8859_16", "johab", "koi8_r", "koi8_u", "mac_cyrillic", "mac_greek", "mac_iceland", "mac_latin2", "mac_roman", "mac_turkish", "ptcp154", "shift_jis", "shift_jis_2004", "shift_jisx0213", "utf_32", "utf_32_be", "utf_32_le", "utf_16", "utf_16_be", "utf_16_le", "utf_7", "utf_8", "utf_8_sig"] class WinCSV(NoModal): def __init__(self, *args, **kwargs): super(WinCSV, self).__init__(*args, **kwargs) self.dialog = Gtk.Dialog( transient_for=self.parent, destroy_with_parent=True) Main().add_window(self.dialog) self.dialog.set_position(Gtk.WindowPosition.CENTER_ON_PARENT) self.dialog.set_icon(TRYTON_ICON) self.dialog.set_default_size(*self.default_size()) self.dialog.connect('response', self.response) dialog_vbox = Gtk.VBox() hbox_mapping = Gtk.HBox(homogeneous=True) dialog_vbox.pack_start(hbox_mapping, expand=True, fill=True, padding=0) frame_fields = Gtk.Frame() frame_fields.set_shadow_type(Gtk.ShadowType.NONE) viewport_fields = Gtk.Viewport() scrolledwindow_fields = Gtk.ScrolledWindow() scrolledwindow_fields.set_policy( Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC) viewport_fields.add(scrolledwindow_fields) frame_fields.add(viewport_fields) label_all_fields = Gtk.Label( label=_('All fields'), use_markup=True) frame_fields.set_label_widget(label_all_fields) hbox_mapping.pack_start( frame_fields, expand=True, fill=True, padding=0) vbox_buttons = Gtk.VBox(homogeneous=False, spacing=10) vbox_buttons.set_border_width(5) hbox_mapping.pack_start( vbox_buttons, expand=False, fill=True, padding=0) button_add = Gtk.Button( label=_('_Add'), stock=None, use_underline=True) button_add.set_image(IconFactory.get_image( 'tryton-add', Gtk.IconSize.BUTTON)) button_add.set_always_show_image(True) button_add.connect_after('clicked', self.sig_sel) vbox_buttons.pack_start( button_add, expand=False, fill=False, padding=0) button_remove = Gtk.Button( label=_('_Remove'), stock=None, use_underline=True) button_remove.set_image(IconFactory.get_image( 'tryton-remove', Gtk.IconSize.BUTTON)) button_remove.set_always_show_image(True) button_remove.connect_after('clicked', self.sig_unsel) vbox_buttons.pack_start( button_remove, expand=False, fill=False, padding=0) button_remove_all = Gtk.Button( label=_('_Clear'), stock=None, use_underline=True) button_remove_all.set_image(IconFactory.get_image( 'tryton-clear', Gtk.IconSize.BUTTON)) button_remove_all.set_always_show_image(True) button_remove_all.connect_after('clicked', self.sig_unsel_all) vbox_buttons.pack_start( button_remove_all, expand=False, fill=False, padding=0) hseparator_buttons = Gtk.HSeparator() vbox_buttons.pack_start( hseparator_buttons, expand=False, fill=False, padding=3) self.add_buttons(vbox_buttons) frame_fields_selected = Gtk.Frame() frame_fields_selected.set_shadow_type(Gtk.ShadowType.NONE) viewport_fields_selected = Gtk.Viewport() scrolledwindow_fields_selected = Gtk.ScrolledWindow() scrolledwindow_fields_selected.set_policy( Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC) viewport_fields_selected.add(scrolledwindow_fields_selected) frame_fields_selected.add(viewport_fields_selected) label_fields_selected = Gtk.Label( label=_('Fields selected'), use_markup=True) frame_fields_selected.set_label_widget(label_fields_selected) hbox_mapping.pack_start( frame_fields_selected, expand=True, fill=True, padding=0) vbox_csv_param = Gtk.VBox() vbox_csv_param.props.margin = 7 dialog_vbox.pack_start( vbox_csv_param, expand=False, fill=True, padding=0) self.add_chooser(vbox_csv_param) expander_csv = Gtk.Expander() vbox_csv_param.pack_start( expander_csv, expand=False, fill=True, padding=0) label_csv_param = Gtk.Label(label=_('CSV Parameters')) expander_csv.set_label_widget(label_csv_param) box = Gtk.HBox(spacing=3) expander_csv.add(box) label_csv_delimiter = Gtk.Label( label=_('Delimiter:'), halign=Gtk.Align.END) box.pack_start(label_csv_delimiter, expand=False, fill=True, padding=0) self.csv_delimiter = Gtk.Entry() self.csv_delimiter.set_max_length(1) if os.name == 'nt' and ',' == locale.localeconv()['decimal_point']: delimiter = ';' else: delimiter = ',' self.csv_delimiter.set_text(delimiter) self.csv_delimiter.set_width_chars(1) label_csv_delimiter.set_mnemonic_widget(self.csv_delimiter) box.pack_start( self.csv_delimiter, expand=False, fill=True, padding=0) label_csv_quotechar = Gtk.Label( label=_("Quote char:"), halign=Gtk.Align.END) box.pack_start(label_csv_quotechar, expand=False, fill=True, padding=0) self.csv_quotechar = Gtk.Entry() self.csv_quotechar.set_text("\"") self.csv_quotechar.set_width_chars(1) label_csv_quotechar.set_mnemonic_widget(self.csv_quotechar) box.pack_start(self.csv_quotechar, expand=False, fill=True, padding=0) label_csv_enc = Gtk.Label( label=_("Encoding:"), halign=Gtk.Align.END) box.pack_start(label_csv_enc, expand=False, fill=True, padding=0) self.csv_enc = Gtk.ComboBoxText() for i, encoding in enumerate(encodings): self.csv_enc.append_text(encoding) if encoding == 'utf_8_sig': self.csv_enc.set_active(i) label_csv_enc.set_mnemonic_widget(self.csv_enc) box.pack_start(self.csv_enc, expand=False, fill=True, padding=0) self.csv_locale = Gtk.CheckButton(label=_("Use locale format")) self.csv_locale.set_active(True) box.pack_start(self.csv_locale, expand=False, fill=True, padding=0) self.add_csv_header_param(box) button_cancel = self.dialog.add_button( set_underline(_("Cancel")), Gtk.ResponseType.CANCEL) button_cancel.set_image(IconFactory.get_image( 'tryton-cancel', Gtk.IconSize.BUTTON)) button_ok = self.dialog.add_button( set_underline(_("OK")), Gtk.ResponseType.OK) button_ok.set_image(IconFactory.get_image( 'tryton-ok', Gtk.IconSize.BUTTON)) self.dialog.vbox.pack_start( dialog_vbox, expand=True, fill=True, padding=0) self.view1 = Gtk.TreeView() self.view1.get_selection().set_mode(Gtk.SelectionMode.MULTIPLE) self.view1.connect('row-expanded', self.on_row_expanded) scrolledwindow_fields.add(self.view1) self.view2 = Gtk.TreeView() self.view2.get_selection().set_mode(Gtk.SelectionMode.MULTIPLE) scrolledwindow_fields_selected.add(self.view2) self.view1.set_headers_visible(False) self.view2.set_headers_visible(False) cell = Gtk.CellRendererText() column = Gtk.TreeViewColumn(_('Field name'), cell, text=0) self.view1.append_column(column) cell = Gtk.CellRendererText() column = Gtk.TreeViewColumn(_('Field name'), cell, text=0) self.view2.append_column(column) self.model1 = Gtk.TreeStore(GObject.TYPE_STRING, GObject.TYPE_STRING) self.model2 = Gtk.ListStore(GObject.TYPE_STRING, GObject.TYPE_STRING) self.model_populate(self._get_fields(self.model)) self.view1.set_model(self.model1) self.view1.connect('row-activated', self.sig_sel) self.view2.set_model(self.model2) self.view2.connect('row-activated', self.sig_unsel) self.dialog.show_all() self.show() self.register() if sys.platform != 'darwin': self.view2.drag_source_set( Gdk.ModifierType.BUTTON1_MASK | Gdk.ModifierType.BUTTON3_MASK, [Gtk.TargetEntry.new( 'EXPORT_TREE', Gtk.TargetFlags.SAME_WIDGET, 0)], Gdk.DragAction.MOVE) self.view2.drag_dest_set( Gtk.DestDefaults.ALL, [Gtk.TargetEntry.new( 'EXPORT_TREE', Gtk.TargetFlags.SAME_WIDGET, 0)], Gdk.DragAction.MOVE) self.view2.connect('drag-begin', self.drag_begin) self.view2.connect('drag-motion', self.drag_motion) self.view2.connect('drag-drop', self.drag_drop) self.view2.connect("drag-data-get", self.drag_data_get) self.view2.connect('drag-data-received', self.drag_data_received) self.view2.connect('drag-data-delete', self.drag_data_delete) drag_column = Gtk.TreeViewColumn() drag_column.set_sizing(Gtk.TreeViewColumnSizing.FIXED) cell_pixbuf = Gtk.CellRendererPixbuf() cell_pixbuf.props.pixbuf = IconFactory.get_pixbuf('tryton-drag') drag_column.pack_start(cell_pixbuf, expand=False) self.view2.insert_column(drag_column, 0) def drag_begin(self, treeview, context): return True def drag_motion(self, treeview, context, x, y, time): try: treeview.set_drag_dest_row(*treeview.get_dest_row_at_pos(x, y)) except TypeError: treeview.set_drag_dest_row( Gtk.TreePath(len(treeview.get_model()) - 1), Gtk.TreeViewDropPosition.AFTER) Gdk.drag_status(context, Gdk.DragAction.MOVE, time) return True def drag_drop(self, treeview, context, x, y, time): treeview.stop_emission_by_name('drag-drop') return True def drag_data_get(self, treeview, context, selection, target_id, etime): treeview.stop_emission_by_name('drag-data-get') def _func_sel_get(store, path, iter_, data): data.append(path[0]) data = [] treeselection = treeview.get_selection() treeselection.selected_foreach(_func_sel_get, data) if not data: return data = ','.join(str(x) for x in data) selection.set(selection.get_target(), 8, data.encode('utf-8')) return True def drag_data_received(self, treeview, context, x, y, selection, info, etime): treeview.stop_emission_by_name('drag-data-received') selection_data = selection.get_data() if not selection_data: return selection_data = selection_data.decode('utf-8') store = treeview.get_model() data_iters = [store.get_iter((int(i),)) for i in selection_data.split(',')] drop_info = treeview.get_dest_row_at_pos(x, y) if drop_info: path, position = drop_info pos = store.get_iter(path) else: pos = store.get_iter((len(store) - 1,)) position = Gtk.TreeViewDropPosition.AFTER if position == Gtk.TreeViewDropPosition.AFTER: data_iters = reversed(data_iters) for item in data_iters: if position == Gtk.TreeViewDropPosition.BEFORE: store.move_before(item, pos) else: store.move_after(item, pos) Gdk.drop_finish(context, False, etime) return True def drag_data_delete(self, treeview, context): treeview.stop_emission_by_name('drag-data-delete') def get_delimiter(self): return self.csv_delimiter.get_text() or ',' def get_quotechar(self): return self.csv_quotechar.get_text() or '"' def get_encoding(self): return self.csv_enc.get_active_text() or 'utf_8' def destroy(self): super(WinCSV, self).destroy() self.dialog.destroy() def show(self): self.dialog.show() def hide(self): self.dialog.hide() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1707150267.0 tryton-7.0.24/tryton/gui/window/win_export.py0000644000175000017500000005132514560205673017473 0ustar00cedced# This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. import base64 import csv import datetime import gettext import json import locale import os import tempfile import urllib.parse from itertools import zip_longest from numbers import Number from gi.repository import Gdk, GObject, Gtk import tryton.common as common from tryton.common import RPCException, RPCExecute from tryton.config import CONFIG from tryton.gui.window.win_csv import WinCSV from tryton.jsonrpc import JSONEncoder from tryton.rpc import CONNECTION, clear_cache _ = gettext.gettext class WinExport(WinCSV): "Window export" def __init__(self, name, screen): self.name = name self.screen = screen self.fields = {} super(WinExport, self).__init__() self.dialog.set_title(_('CSV Export: %s') % name) # Hide as selected record is the default self.ignore_search_limit.hide() @property def model(self): return self.screen.model_name @property def context(self): return self.screen.context @property def screen_is_tree(self): return bool( self.screen.current_view and self.screen.current_view.view_type == 'tree' and self.screen.current_view.children_field) def add_buttons(self, box): button_save_export = Gtk.Button( label=_('_Save Export'), stock=None, use_underline=True) button_save_export.set_image(common.IconFactory.get_image( 'tryton-save', Gtk.IconSize.BUTTON)) button_save_export.set_always_show_image(True) button_save_export.connect_after('clicked', self.addreplace_predef) box.pack_start( button_save_export, expand=False, fill=False, padding=0) button_url = Gtk.MenuButton( label=_("_URL Export"), stock=None, use_underline=True) button_url.set_image(common.IconFactory.get_image( 'tryton-public', Gtk.IconSize.BUTTON)) button_url.set_always_show_image(True) box.pack_start(button_url, expand=False, fill=False, padding=0) menu_url = Gtk.Menu() menuitem_url = Gtk.MenuItem() menuitem_url.connect('activate', self.copy_url) menu_url.add(menuitem_url) menu_url.show_all() button_url.set_popup(menu_url) button_url.connect('button-press-event', self.set_url, menuitem_url) button_del_export = Gtk.Button( label=_('_Delete Export'), stock=None, use_underline=True) button_del_export.set_image(common.IconFactory.get_image( 'tryton-delete', Gtk.IconSize.BUTTON)) button_del_export.set_always_show_image(True) button_del_export.connect_after('clicked', self.remove_predef) box.pack_start(button_del_export, expand=False, fill=False, padding=0) frame_predef_exports = Gtk.Frame() frame_predef_exports.set_border_width(2) frame_predef_exports.set_shadow_type(Gtk.ShadowType.NONE) box.pack_start(frame_predef_exports, expand=True, fill=True, padding=0) viewport_exports = Gtk.Viewport() scrolledwindow_exports = Gtk.ScrolledWindow() scrolledwindow_exports.set_policy( Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC) label_predef_exports = Gtk.Label( label=_("Predefined exports"), use_markup=True) frame_predef_exports.set_label_widget(label_predef_exports) viewport_exports.add(scrolledwindow_exports) frame_predef_exports.add(viewport_exports) self.pref_export = Gtk.TreeView() self.pref_export.append_column( Gtk.TreeViewColumn(_('Name'), Gtk.CellRendererText(), text=1)) scrolledwindow_exports.add(self.pref_export) self.pref_export.connect("button-press-event", self.export_click) self.pref_export.connect("key-press-event", self.export_keypress) self.predef_model = Gtk.ListStore( GObject.TYPE_INT, GObject.TYPE_STRING, GObject.TYPE_PYOBJECT, GObject.TYPE_PYOBJECT) self.fill_predefwin() def add_chooser(self, box): hbox_csv_export = Gtk.HBox() box.pack_start(hbox_csv_export, expand=False, fill=True, padding=0) self.saveas = Gtk.ComboBoxText() hbox_csv_export.pack_start( self.saveas, expand=True, fill=True, padding=3) self.saveas.append_text(_("Open")) self.saveas.append_text(_("Save")) self.saveas.set_active(0) self.selected_records = Gtk.ComboBoxText() hbox_csv_export.pack_start( self.selected_records, expand=True, fill=True, padding=3) self.selected_records.append_text(_("Listed Records")) self.selected_records.append_text(_("Selected Records")) if not self.screen_is_tree: self.selected_records.set_active(1) else: self.selected_records.set_active(0) self.ignore_search_limit = Gtk.CheckButton( label=_("Ignore search limit")) hbox_csv_export.pack_start( self.ignore_search_limit, expand=False, fill=True, padding=3) self.selected_records.connect( 'changed', lambda w: self.ignore_search_limit.set_visible( not w.get_active() and not self.screen_is_tree)) def add_csv_header_param(self, box): self.add_field_names = Gtk.CheckButton(label=_("Add field names")) self.add_field_names.set_active(True) box.pack_start( self.add_field_names, expand=False, fill=True, padding=0) def model_populate(self, fields, parent_node=None, prefix_field='', prefix_name=''): def key(n_f): return n_f[1].get('string') or n_f[0] for name, field in sorted(list(fields.items()), key=key, reverse=True): string_ = field['string'] or name items = [(name, field, string_)] if field['type'] == 'selection': items.insert(0, ('%s.translated' % name, field, _('%s (string)') % string_)) elif field['type'] == 'reference': items.insert(0, ('%s.translated' % name, field, _('%s (model name)') % string_)) items.insert(0, ('%s/rec_name' % name, field, _("%s/Record Name") % string_)) for name, field, string_ in items: path = prefix_field + name node = self.model1.insert(parent_node, 0, [string_, path]) string_ = prefix_name + string_ self.fields[path] = (string_, field.get('relation')) # Insert relation only to real field if '.' not in name: if field.get('relation'): self.model1.insert(node, 0, [None, '']) def _get_fields(self, model): try: return RPCExecute('model', model, 'fields_get', None, context=self.context) except RPCException: return '' def on_row_expanded(self, treeview, iter, path): child = self.model1.iter_children(iter) if child and self.model1.get_value(child, 0) is None: prefix_field = self.model1.get_value(iter, 1) string_, relation = self.fields[prefix_field] self.model_populate(self._get_fields(relation), iter, prefix_field + '/', string_ + '/') self.model1.remove(child) def sig_sel(self, *args): sel = self.view1.get_selection() sel.selected_foreach(self._sig_sel_add) def _sig_sel_add(self, store, path, iter): name = store.get_value(iter, 1) self.sel_field(name) def sig_unsel(self, *args): store, paths = self.view2.get_selection().get_selected_rows() # Convert first into TreeIter before removing from the store iters = [store.get_iter(p) for p in paths] for i in iters: store.remove(i) def sig_unsel_all(self, *args): self.model2.clear() def fill_predefwin(self): try: exports = RPCExecute( 'model', 'ir.export', 'get', self.model, ['name', 'header', 'records', 'export_fields.name'], context=self.context) except RPCException: return for export in exports: self.predef_model.append(( export['id'], export['name'], [f['name'] for f in export['export_fields.']], export)) self.pref_export.set_model(self.predef_model) def addreplace_predef(self, widget): iter = self.model2.get_iter_first() fields = [] while iter: field_name = self.model2.get_value(iter, 1) fields.append(field_name) iter = self.model2.iter_next(iter) if not fields: return selection = self.pref_export.get_selection().get_selected() if selection is None: return model, iter_ = selection if iter_ is None: pref_id = None name = common.ask(_('What is the name of this export?')) if not name: return else: pref_id = model.get_value(iter_, 0) name = model.get_value(iter_, 1) override = common.sur(_("Override '%s' definition?") % name) if not override: return try: values = { 'header': self.add_field_names.get_active(), 'records': ( 'selected' if self.selected_records.get_active() else 'listed'), } if not pref_id: values.update({ 'name': name, 'resource': self.model, 'export_fields': [{'name': x, } for x in fields], }) pref_id = RPCExecute( 'model', 'ir.export', 'set', values, context=self.context) else: RPCExecute( 'model', 'ir.export', 'update', pref_id, values, fields, context=self.context) except RPCException: return clear_cache('model.%s.view_toolbar_get' % self.model) if iter_ is None: self.predef_model.append((pref_id, name, fields, values)) else: model.set_value(iter_, 2, fields) model.set_value(iter_, 3, values) def remove_predef(self, widget): sel = self.pref_export.get_selection().get_selected() if sel is None: return None (model, i) = sel if not i: return None export_id = model.get_value(i, 0) try: RPCExecute( 'model', 'ir.export', 'unset', export_id, context=self.context) except RPCException: return clear_cache('model.%s.view_toolbar_get' % self.model) for i in range(len(self.predef_model)): if self.predef_model[i][0] == export_id: del self.predef_model[i] break self.pref_export.set_model(self.predef_model) def sel_predef(self, path): self.model2.clear() _, _, fields, values = self.predef_model[path[0]] for name in fields: if name not in self.fields: iter = self.model1.get_iter_first() prefix = '' for parent in name.split('/')[:-1]: while iter: if self.model1.get_value(iter, 1) == \ (prefix + parent): self.on_row_expanded(self.view1, iter, self.model1.get_path(iter)) iter = self.model1.iter_children(iter) prefix += parent + '/' break else: iter = self.model1.iter_next(iter) if name not in self.fields: continue self.sel_field(name) self.add_field_names.set_active(values.get('header')) self.selected_records.set_active( int(values.get('records') == 'selected')) def sel_field(self, name): string_, relation = self.fields[name] if relation: name += '/rec_name' self.model2.append((string_, name)) def response(self, dialog, response): if response == Gtk.ResponseType.OK: fields = [] iter = self.model2.get_iter_first() while iter: fields.append(self.model2.get_value(iter, 1)) iter = self.model2.iter_next(iter) header = self.add_field_names.get_active() if self.selected_records.get_active(): ids = [r.id for r in self.screen.selected_records] paths = self.screen.selected_paths try: data = RPCExecute( 'model', self.model, 'export_data', ids, fields, header, context=self.context) except RPCException: data = [] elif self.screen_is_tree: ids = [r.id for r in self.screen.listed_records] paths = self.screen.listed_paths try: data = RPCExecute( 'model', self.model, 'export_data', ids, fields, header, context=self.context) except RPCException: data = [] else: paths = None domain = self.screen.search_domain( self.screen.screen_container.get_text()) if self.ignore_search_limit.get_active(): offset, limit = 0, None else: offset, limit = self.screen.offset, self.screen.limit try: data = RPCExecute( 'model', self.model, 'export_data_domain', domain, fields, offset, limit, self.screen.order, header, context=self.context) except RPCException: data = [] if self.saveas.get_active(): fname = common.file_selection(_('Save As...'), action=Gtk.FileChooserAction.SAVE) if fname: self.export_csv(fname, data, paths, header=header) else: fileno, fname = tempfile.mkstemp( '.csv', common.slugify(self.name) + '_') if self.export_csv( fname, data, paths, popup=False, header=header): os.close(fileno) common.file_open(fname, 'csv') else: os.close(fileno) self.destroy() def export_csv(self, fname, data, paths, popup=True, header=False): encoding = self.csv_enc.get_active_text() or 'utf_8_sig' locale_format = self.csv_locale.get_active() try: writer = csv.writer( open(fname, 'w', encoding=encoding, newline=''), quotechar=self.get_quotechar(), delimiter=self.get_delimiter()) for row, path in zip_longest(data, paths or []): indent = len(path) - 1 if path else 0 if row: writer.writerow(self.format_row( row, indent=indent, locale_format=locale_format)) if popup: size = len(data) if header: size -= 1 if size <= 1: common.message(_('%d record saved.') % size) else: common.message(_('%d records saved.') % size) return True except (IOError, UnicodeEncodeError, csv.Error) as exception: common.warning(str(exception), _('Export failed')) return False @classmethod def format_row(cls, line, indent=0, locale_format=True): row = [] for i, val in enumerate(line): if locale_format: if isinstance(val, Number): val = locale.str(val) elif isinstance(val, datetime.datetime): val = common.timezoned_date(val) val = val.strftime(common.date_format() + ' %X') elif isinstance(val, datetime.date): val = val.strftime(common.date_format()) elif isinstance(val, datetime.timedelta): val = common.timedelta.format( val, {'s': 1, 'm': 60, 'h': 60 * 60}) elif isinstance(val, datetime.timedelta): val = val.total_seconds() elif isinstance(val, bool): val = int(val) if i == 0 and indent and isinstance(val, str): val = ' ' * indent + val if isinstance(val, bytes): val = base64.b64encode(val).decode('utf-8') row.append(val) return row def export_click(self, treeview, event): path_at_pos = treeview.get_path_at_pos(int(event.x), int(event.y)) if not path_at_pos or event.button != 1: return path, col, x, y = path_at_pos selection = treeview.get_selection() if event.type == Gdk.EventType._2BUTTON_PRESS: self.sel_predef(path) selection.select_path(path) return True elif selection.path_is_selected(path): selection.unselect_path(path) return True def export_keypress(self, treeview, event): if event.keyval not in [Gdk.KEY_Return, Gdk.KEY_space]: return model, selected = treeview.get_selection().get_selected() if not selected: return self.sel_predef(model.get_path(selected)) def get_url(self): path = [CONFIG['login.db'], 'data', self.model] protocol = 'https' if CONNECTION.ssl else 'http' host = common.get_hostname(CONFIG['login.host']) port = common.get_port(CONFIG['login.host']) if protocol == 'https' and port == 445: netloc = host elif protocol == 'http' and port == 80: netloc = host else: netloc = '%s:%s' % (host, port) query_string = [] if self.selected_records.get_active(): domain = [r.id for r in self.screen.selected_records] else: domain = self.screen.search_domain( self.screen.screen_container.get_text()) if (not self.ignore_search_limit.get_active() and self.screen.limit is not None): query_string.append(('s', str(self.screen.limit))) query_string.append( ('p', str(self.screen.offset // self.screen.limit))) if self.screen.order: for expr in self.screen.order: query_string.append(('o', ','.join(filter(None, expr)))) query_string.insert(0, ('d', json.dumps( domain, cls=JSONEncoder, separators=(',', ':')))) if self.screen.local_context: query_string.append(('c', json.dumps( self.screen.local_context, cls=JSONEncoder, separators=(',', ':')))) iter_ = self.model2.get_iter_first() while iter_: query_string.append(('f', self.model2.get_value(iter_, 1))) iter_ = self.model2.iter_next(iter_) encoding = self.csv_enc.get_active_text() if encoding: query_string.append(('enc', encoding)) query_string.append(('dl', self.get_delimiter())) query_string.append(('qc', self.get_quotechar())) if not self.add_field_names.get_active(): query_string.append(('h', '0')) if self.csv_locale.get_active(): query_string.append(('loc', '1')) query_string = urllib.parse.urlencode(query_string) return urllib.parse.urlunparse(( protocol, netloc, '/'.join(path), '', query_string, '')) def set_url(self, button, event, menuitem): url = self.get_url() size = 80 if len(url) > size: url = url[:size // 2] + '...' + url[-size // 2:] menuitem.set_label(url) def copy_url(self, menuitem): url = self.get_url() for selection in [ Gdk.Atom.intern('PRIMARY', True), Gdk.Atom.intern('CLIPBOARD', True), ]: clipboard = Gtk.Clipboard.get(selection) clipboard.set_text(url, -1) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1725720314.0 tryton-7.0.24/tryton/gui/window/win_form.py0000644000175000017500000004575414667063372017134 0ustar00cedced# This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. import gettext from itertools import chain from gi.repository import Gdk, Gtk, Pango import tryton.common as common from tryton.common import MODELNAME, TRYTON_ICON from tryton.common.domain_parser import quote from tryton.common.underline import set_underline from tryton.common.widget_style import widget_class from tryton.gui import Main from tryton.gui.window.nomodal import NoModal from .infobar import InfoBar _ = gettext.gettext class WinForm(NoModal, InfoBar): "Form window" def __init__(self, screen, callback=None, view_type='form', new=False, many=0, domain=None, context=None, save_current=False, title='', defaults=None): tooltips = common.Tooltips() NoModal.__init__(self) self.screen = screen self.callback = callback self.many = many self.domain = domain self.context = context self.save_current = save_current if screen.breadcrumb: breadcrumb = list(screen.breadcrumb) if title: breadcrumb.append(title) self.title = ' › '.join(chain( (common.ellipsize(x, 30) for x in breadcrumb[-3:-1]), breadcrumb[-1:])) if len(breadcrumb) > 3: self.title = '... › ' + self.title else: if not title: title = MODELNAME.get(screen.model_name) self.title = title self.prev_view = self.screen.current_view self.screen.screen_container.alternate_view = True self.screen.switch_view(view_type=view_type) if self.screen.current_view.view_type != view_type: self.destroy() return if new: self.screen.new(defaults=defaults) self.win = Gtk.Dialog( title=_('Link'), transient_for=self.parent, destroy_with_parent=True) Main().add_window(self.win) self.win.set_position(Gtk.WindowPosition.CENTER_ON_PARENT) self.win.set_icon(TRYTON_ICON) self.win.set_deletable(False) self.win.connect('delete-event', lambda *a: True) self.win.connect('close', self.close) self.win.connect('response', self.response) self.win.set_default_size(*self.default_size()) self.accel_group = Gtk.AccelGroup() self.win.add_accel_group(self.accel_group) readonly = self.screen.group.readonly self.but_ok = None self.but_new = None self._initial_value = None self.view_type = view_type if view_type == 'form' and not readonly: if new: label, icon = _("Delete"), 'tryton-delete' else: label, icon = _("Discard changes"), 'tryton-cancel' record = self.screen.current_record self._initial_value = record.get_on_change_value() if record.parent and record.parent_name in record.group.fields: parent_field = record.group.fields[record.parent_name] self._initial_value[record.parent_name] = ( parent_field.get_eval(record)) self.but_cancel = self.win.add_button( set_underline(label), Gtk.ResponseType.CANCEL) self.but_cancel.set_image(common.IconFactory.get_image( icon, Gtk.IconSize.BUTTON)) self.but_cancel.set_always_show_image(True) if new and self.many: if self.save_current and not readonly: label = _("Save and New") else: label = _("Add and New") self.but_new = self.win.add_button( set_underline(label), Gtk.ResponseType.ACCEPT) self.but_new.set_image(common.IconFactory.get_image( 'tryton-create', Gtk.IconSize.BUTTON)) self.but_new.set_always_show_image(True) self.but_new.set_accel_path('/Form/New', self.accel_group) if self.save_current and not readonly: self.but_ok = Gtk.Button( label=set_underline(_("Save")), use_underline=True) self.but_ok.set_image(common.IconFactory.get_image( 'tryton-save', Gtk.IconSize.BUTTON)) self.but_ok.set_always_show_image(True) self.but_ok.set_accel_path('/Form/Save', self.accel_group) self.but_ok.set_can_default(True) self.but_ok.show() self.win.add_action_widget(self.but_ok, Gtk.ResponseType.OK) else: if readonly or view_type == 'tree': label = _("Close") elif new: label = _("Add") else: label = _("Apply changes") self.but_ok = self.win.add_button( set_underline(label), Gtk.ResponseType.OK) self.but_ok.set_image(common.IconFactory.get_image( 'tryton-ok', Gtk.IconSize.BUTTON)) self.but_ok.set_always_show_image(True) self.but_ok.add_accelerator('clicked', self.accel_group, Gdk.KEY_Return, Gdk.ModifierType.CONTROL_MASK, Gtk.AccelFlags.VISIBLE) self.win.set_default_response(Gtk.ResponseType.OK) self.win.set_title(self.title) revision = self.screen.context.get('_datetime') if revision and self.screen.model_name in common.MODELHISTORY: format_ = self.screen.context.get('date_format', '%x') format_ += ' %H:%M:%S.%f' revision_label = ' @ %s' % revision.strftime(format_) label = common.ellipsize( self.title, 80 - len(revision_label)) + revision_label tooltip = self.title + revision_label else: label = common.ellipsize(self.title, 80) tooltip = self.title title = Gtk.Label( label=label, halign=Gtk.Align.START, margin=5, ellipsize=Pango.EllipsizeMode.END) tooltips.set_tip(title, tooltip) title.set_size_request(0, -1) # Allow overflow title.show() hbox = Gtk.HBox() hbox.pack_start(title, expand=True, fill=True, padding=0) hbox.show() frame = Gtk.Frame() frame.set_shadow_type(Gtk.ShadowType.ETCHED_IN) widget_class(frame, 'window-title', True) frame.add(hbox) frame.show() self.win.vbox.pack_start(frame, expand=False, fill=True, padding=3) if view_type == 'tree': hbox = Gtk.HBox(homogeneous=False, spacing=0) hbox.set_halign(Gtk.Align.END) access = common.MODELACCESS[screen.model_name] but_switch = Gtk.Button() tooltips.set_tip(but_switch, _('Switch')) but_switch.connect('clicked', self.switch_view) but_switch.add(common.IconFactory.get_image( 'tryton-switch', Gtk.IconSize.SMALL_TOOLBAR)) but_switch.set_relief(Gtk.ReliefStyle.NONE) hbox.pack_start(but_switch, expand=False, fill=False, padding=0) self.but_pre = Gtk.Button() tooltips.set_tip(self.but_pre, _('Previous')) self.but_pre.connect('clicked', self._sig_previous) self.but_pre.add(common.IconFactory.get_image( 'tryton-back', Gtk.IconSize.SMALL_TOOLBAR)) self.but_pre.set_relief(Gtk.ReliefStyle.NONE) hbox.pack_start(self.but_pre, expand=False, fill=False, padding=0) self.label = Gtk.Label(label='(0,0)') hbox.pack_start(self.label, expand=False, fill=False, padding=0) self.but_next = Gtk.Button() tooltips.set_tip(self.but_next, _('Next')) self.but_next.connect('clicked', self._sig_next) self.but_next.add(common.IconFactory.get_image( 'tryton-forward', Gtk.IconSize.SMALL_TOOLBAR)) self.but_next.set_relief(Gtk.ReliefStyle.NONE) hbox.pack_start(self.but_next, expand=False, fill=False, padding=0) hbox.pack_start( Gtk.VSeparator(), expand=False, fill=True, padding=0) if domain is not None: self.wid_text = Gtk.Entry() self.wid_text.set_property('width_chars', 13) self.wid_text.connect('activate', self._sig_activate) self.wid_text.connect('focus-out-event', self._focus_out) hbox.pack_start( self.wid_text, expand=True, fill=True, padding=0) self.but_add = Gtk.Button() tooltips.set_tip(self.but_add, _('Add')) self.but_add.connect('clicked', self._sig_add) self.but_add.add(common.IconFactory.get_image( 'tryton-add', Gtk.IconSize.SMALL_TOOLBAR)) self.but_add.set_relief(Gtk.ReliefStyle.NONE) hbox.pack_start( self.but_add, expand=False, fill=False, padding=0) if not access['read'] or readonly: self.but_add.set_sensitive(False) self.but_remove = Gtk.Button() tooltips.set_tip(self.but_remove, _('Remove ')) self.but_remove.connect('clicked', self._sig_remove, True) self.but_remove.add(common.IconFactory.get_image( 'tryton-remove', Gtk.IconSize.SMALL_TOOLBAR)) self.but_remove.set_relief(Gtk.ReliefStyle.NONE) hbox.pack_start( self.but_remove, expand=False, fill=False, padding=0) if not access['read'] or readonly: self.but_remove.set_sensitive(False) hbox.pack_start( Gtk.VSeparator(), expand=False, fill=True, padding=0) self.but_new = Gtk.Button() tooltips.set_tip(self.but_new, _('Create a new record ')) self.but_new.connect('clicked', self._sig_new) self.but_new.add(common.IconFactory.get_image( 'tryton-create', Gtk.IconSize.SMALL_TOOLBAR)) self.but_new.set_relief(Gtk.ReliefStyle.NONE) hbox.pack_start(self.but_new, expand=False, fill=False, padding=0) if not access['create'] or readonly: self.but_new.set_sensitive(False) self.but_del = Gtk.Button() tooltips.set_tip(self.but_del, _('Delete selected record ')) self.but_del.connect('clicked', self._sig_remove, False) self.but_del.add(common.IconFactory.get_image( 'tryton-delete', Gtk.IconSize.SMALL_TOOLBAR)) self.but_del.set_relief(Gtk.ReliefStyle.NONE) hbox.pack_start(self.but_del, expand=False, fill=False, padding=0) if not access['delete'] or readonly: self.but_del.set_sensitive(False) self.but_undel = Gtk.Button() tooltips.set_tip(self.but_undel, _('Undelete selected record ')) self.but_undel.connect('clicked', self._sig_undelete) self.but_undel.add(common.IconFactory.get_image( 'tryton-undo', Gtk.IconSize.SMALL_TOOLBAR)) self.but_undel.set_relief(Gtk.ReliefStyle.NONE) hbox.pack_start( self.but_undel, expand=False, fill=False, padding=0) if not access['delete'] or readonly: self.but_undel.set_sensitive(False) but_switch.props.sensitive = screen.number_of_views > 1 tooltips.enable() hbox.show_all() self.win.vbox.pack_start( hbox, expand=False, fill=True, padding=0) scroll = Gtk.ScrolledWindow() scroll.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC) scroll.set_placement(Gtk.CornerType.TOP_LEFT) scroll.set_shadow_type(Gtk.ShadowType.NONE) scroll.show() self.win.vbox.pack_start(scroll, expand=True, fill=True, padding=0) scroll.add(self.screen.screen_container.alternate_viewport) self.win.vbox.pack_start( self.create_info_bar(), expand=False, fill=True, padding=0) self.screen.windows.append(self) if view_type == 'tree': self.screen.screen_container.alternate_viewport.connect( 'key-press-event', self.on_keypress) self.register() self.show() self.screen.display() self.screen.current_view.set_cursor() def on_keypress(self, widget, event): if ((event.keyval == Gdk.KEY_F3) and self.but_new.get_property('sensitive')): self._sig_new(widget) return False if (event.keyval in [Gdk.KEY_Delete, Gdk.KEY_KP_Delete] and widget == self.screen.screen_container.alternate_viewport): self._sig_remove(widget) return False def switch_view(self, widget): self.screen.switch_view() def _sig_new(self, widget): self.screen.new() self.screen.current_view.widget.set_sensitive(True) def _sig_next(self, widget): self.screen.display_next() def _sig_previous(self, widget): self.screen.display_prev() def _sig_remove(self, widget, remove=False): self.screen.remove(remove=remove) def _sig_undelete(self, button): self.screen.unremove() def _sig_activate(self, *args): self._sig_add() self.wid_text.grab_focus() def _focus_out(self, *args): if self.wid_text.get_text(): self._sig_add() def _sig_add(self, *args): from tryton.gui.window.win_search import WinSearch domain = self.domain[:] model_name = self.screen.model_name value = self.wid_text.get_text() def callback(result): if result: ids = [x[0] for x in result] self.screen.load(ids, modified=True) self.screen.set_cursor() self.wid_text.set_text('') win = WinSearch(model_name, callback, sel_multi=True, context=self.context, domain=domain) win.screen.search_filter(quote(value)) win.show() def record_message(self, position, size, *args): if self.view_type != 'tree': return name = '_' access = common.MODELACCESS[self.screen.model_name] deletable = True if self.screen.current_record: deletable = self.screen.current_record.deletable readonly = self.screen.group.readonly if position >= 1: name = str(position) if self.domain is not None: self.but_remove.set_sensitive(True) if position < size: self.but_next.set_sensitive(True) else: self.but_next.set_sensitive(False) if position > 1: self.but_pre.set_sensitive(True) else: self.but_pre.set_sensitive(False) self.but_del.set_sensitive(bool( not readonly and access['delete'] and deletable)) self.but_undel.set_sensitive(bool(not readonly)) else: self.but_del.set_sensitive(False) self.but_undel.set_sensitive(False) self.but_next.set_sensitive(False) self.but_pre.set_sensitive(False) if self.domain is not None: self.but_remove.set_sensitive(False) line = '(%s/%s)' % (name, position) self.label.set_text(line) def record_modified(self, *args): self.info_bar_refresh() def close(self, widget): widget.stop_emission_by_name('close') self.response(self.win, Gtk.ResponseType.CANCEL) return True def response(self, win, response_id): validate = False cancel_responses = [ Gtk.ResponseType.CANCEL, Gtk.ResponseType.DELETE_EVENT] self.screen.current_view.set_value() readonly = self.screen.group.readonly if (response_id not in cancel_responses and not readonly and self.screen.current_record is not None): validate = self.screen.current_record.validate( self.screen.current_view.get_fields()) if validate and self.screen.pre_validate: validate = self.screen.current_record.pre_validate() if validate and self.save_current: if not self.screen.save_current(): validate = False elif validate and self.screen.current_view.view_type == 'form': view = self.screen.current_view for widgets in view.widgets.values(): for widget in widgets: if (hasattr(widget, 'screen') and widget.screen.pre_validate): record = widget.screen.current_record if record: validate = record.pre_validate() if not validate: self.info_bar_add( self.screen.invalid_message(), Gtk.MessageType.ERROR) self.screen.set_cursor() self.screen.display() return self.info_bar_clear() if response_id == Gtk.ResponseType.ACCEPT: self.new() return if (self.screen.current_record and not readonly and response_id in cancel_responses): record = self.screen.current_record added = 'id' in record.modified_fields if (self.screen.current_record.id < 0 or self.save_current): self.screen.cancel_current(self._initial_value) elif record.modified: record.cancel() record.reload() record.set_modified() if added: record.modified_fields.setdefault('id') result = False else: result = (response_id not in cancel_responses) and not readonly if self.callback: self.callback(result) self.destroy() def new(self): self.screen.new() self._initial_value = None self.screen.current_view.display() self.screen.set_cursor(new=True) self.many -= 1 if self.many == 0: self.but_new.set_sensitive(False) self.win.set_default_response(Gtk.ResponseType.OK) def destroy(self): try: self.screen.windows.remove(self) except ValueError: pass self.screen.screen_container.alternate_view = False viewport = self.screen.screen_container.alternate_viewport if viewport and viewport.get_parent(): viewport.get_parent().remove(viewport) self.screen.switch_view(view_type=self.prev_view.view_type) if getattr(self, 'win', None): self.win.destroy() NoModal.destroy(self) def show(self): self.win.show() def hide(self): self.win.hide() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1725720314.0 tryton-7.0.24/tryton/gui/window/win_import.py0000644000175000017500000002260114667063372017465 0ustar00cedced# This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. import base64 import csv import datetime as dt import gettext import locale from decimal import Decimal from gi.repository import Gtk import tryton.common as common from tryton.common import RPCException, RPCExecute from tryton.gui.window.win_csv import WinCSV _ = gettext.gettext class WinImport(WinCSV): "Window import" def __init__(self, name, model, context): self.name = name self.model = model self.context = context self.fields_data = {} self.fields = {} self.fields_invert = {} super(WinImport, self).__init__() self.dialog.set_title(_('CSV Import: %s') % name) def add_buttons(self, box): button_autodetect = Gtk.Button( label=_('_Auto-Detect'), stock=None, use_underline=True) button_autodetect.set_image(common.IconFactory.get_image( 'tryton-search', Gtk.IconSize.BUTTON)) button_autodetect.set_always_show_image(True) button_autodetect.connect_after('clicked', self.sig_autodetect) box.pack_start( button_autodetect, expand=False, fill=False, padding=0) def add_chooser(self, box): hbox_csv_import = Gtk.HBox() box.pack_start(hbox_csv_import, expand=False, fill=True, padding=4) label_csv_import = Gtk.Label(label=_("File to Import:")) hbox_csv_import.pack_start(label_csv_import, False, False, 0) self.import_csv_file = Gtk.FileChooserButton(title=_("Open...")) label_csv_import.set_mnemonic_widget(self.import_csv_file) hbox_csv_import.pack_start( self.import_csv_file, expand=True, fill=True, padding=0) def add_csv_header_param(self, box): label_csv_skip = Gtk.Label( label=_('Lines to Skip:'), halign=Gtk.Align.START) box.pack_start(label_csv_skip, expand=False, fill=True, padding=0) self.csv_skip = Gtk.SpinButton() self.csv_skip.configure(Gtk.Adjustment( value=0, lower=0, upper=100, step_increment=1, page_increment=10), 1, 0) label_csv_skip.set_mnemonic_widget(self.csv_skip) box.pack_start(self.csv_skip, expand=False, fill=True, padding=0) def model_populate(self, fields, parent_node=None, prefix_field='', prefix_name=''): fields_order = list(fields.keys()) fields_order.sort( key=lambda x: fields[x].get('string', ''), reverse=True) for field in fields_order: if not fields[field].get('readonly', False) or field == 'id': self.fields_data[prefix_field + field] = fields[field] name = fields[field]['string'] or field node = self.model1.insert( parent_node, 0, [name, prefix_field + field]) name = prefix_name + name # Only One2Many can be nested for import if fields[field]['type'] == 'one2many': relation = fields[field].get('relation') else: relation = None self.fields[prefix_field + field] = (name, relation) self.fields_invert[name] = prefix_field + field if relation: self.model1.insert(node, 0, [None, '']) def _get_fields(self, model): try: return RPCExecute('model', model, 'fields_get', None, context=self.context) except RPCException: return '' def on_row_expanded(self, treeview, iter, path): child = self.model1.iter_children(iter) # autodetect could call for node without children if child is None: return if self.model1.get_value(child, 0) is None: prefix_field = self.model1.get_value(iter, 1) name, model = self.fields[prefix_field] self.model_populate( self._get_fields(model), iter, prefix_field + '/', name + '/') self.model1.remove(child) def sig_autodetect(self, widget=None): fname = self.import_csv_file.get_filename() if not fname: common.message(_('You must select an import file first.')) return True encoding = self.get_encoding() self.csv_skip.set_value(1) try: data = csv.reader( open(fname, 'r', encoding=encoding, newline=''), quotechar=self.get_quotechar(), delimiter=self.get_delimiter()) except (IOError, UnicodeDecodeError, csv.Error) as exception: common.warning(str(exception), _("Detection failed")) return True self.sig_unsel_all() word = '' for line in data: for word in line: if word not in self.fields_invert and word not in self.fields: iter = self.model1.get_iter_first() prefix = '' for parent in word.split('/')[:-1]: while iter: if self.model1.get_value(iter, 0) == parent or \ self.model1.get_value(iter, 1) == \ (prefix + parent): self.on_row_expanded(self.view1, iter, self.model1.get_path(iter)) iter = self.model1.iter_children(iter) prefix = parent + '/' break else: iter = self.model1.iter_next(iter) if word in self.fields_invert: name = word field = self.fields_invert[word] elif word in self.fields: name = self.fields[word][0] field = word else: common.warning( _('Unknown column header "%s"') % word, _('Error')) return True num = self.model2.append() self.model2.set(num, 0, name, 1, field) break return True def sig_sel(self, *args): sel = self.view1.get_selection() sel.selected_foreach(self._sig_sel_add) def _sig_sel_add(self, store, path, iter): num = self.model2.append() name = self.fields[store.get_value(iter, 1)][0] self.model2.set(num, 0, name, 1, store.get_value(iter, 1)) def sig_unsel(self, *args): store, paths = self.view2.get_selection().get_selected_rows() # Convert first into TreeIter before removing from the store iters = [store.get_iter(p) for p in paths] for i in iters: store.remove(i) def sig_unsel_all(self, *args): self.model2.clear() def response(self, dialog, response): if response == Gtk.ResponseType.OK: fields = [] iter = self.model2.get_iter_first() while iter: fields.append(self.model2.get_value(iter, 1)) iter = self.model2.iter_next(iter) fname = self.import_csv_file.get_filename() if fname: self.import_csv(fname, fields) self.destroy() def import_csv(self, fname, fields): # TODO: make it works with references skip = self.csv_skip.get_value_as_int() encoding = self.get_encoding() locale_format = self.csv_locale.get_active() try: reader = csv.reader( open(fname, 'r', encoding=encoding), quotechar=self.get_quotechar(), delimiter=self.get_delimiter()) data = [] for i, line in enumerate(reader): if i < skip or not line: continue row = [] for field, val in zip(fields, line): if locale_format and val: type_ = self.fields_data[field]['type'] if type_ == 'integer': val = locale.atoi(val) elif type_ == 'float': val = locale.atof(val) elif type_ == 'numeric': val = Decimal(locale.delocalize(val)) elif type_ == 'date': val = dt.datetime.strptime( val, common.date_format()).date() elif type_ == 'datetime': val = dt.datetime.strptime( val, common.date_format() + ' %X') elif type_ == 'binary': val = base64.b64decode(val) row.append(val) data.append(row) except (IOError, UnicodeDecodeError, csv.Error, ValueError) \ as exception: common.warning(str(exception), _("Import failed")) return try: count = RPCExecute( 'model', self.model, 'import_data', fields, data, context=self.context) except RPCException: return if count == 1: common.message(_('%d record imported.') % count) else: common.message(_('%d records imported.') % count) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685599.0 tryton-7.0.24/tryton/gui/window/win_search.py0000644000175000017500000001352714517761237017426 0ustar00cedced# This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. import gettext from gi.repository import Gdk, Gtk import tryton.common as common from tryton.common.underline import set_underline from tryton.config import TRYTON_ICON from tryton.gui import Main from tryton.gui.window.nomodal import NoModal from tryton.gui.window.view_form.screen import Screen from tryton.gui.window.win_form import WinForm _ = gettext.gettext class WinSearch(NoModal): def __init__(self, model, callback, sel_multi=True, context=None, domain=None, order=None, view_ids=None, views_preload=None, new=True, title='', exclude_field=None): NoModal.__init__(self) if view_ids is None: view_ids = [] if views_preload is None: views_preload = {} self.domain = domain or [] self.context = context or {} self.order = order self.view_ids = view_ids self.views_preload = views_preload self.sel_multi = sel_multi self.callback = callback if not title: title = common.MODELNAME.get(model) self.title = title self.exclude_field = exclude_field self.win = Gtk.Dialog( title=_('Search'), transient_for=self.parent, destroy_with_parent=True) Main().add_window(self.win) self.win.set_icon(TRYTON_ICON) self.win.set_position(Gtk.WindowPosition.CENTER_ON_PARENT) self.win.set_default_response(Gtk.ResponseType.APPLY) self.win.connect('response', self.response) self.win.set_default_size(*self.default_size()) self.accel_group = Gtk.AccelGroup() self.win.add_accel_group(self.accel_group) self.but_cancel = self.win.add_button( set_underline(_("Cancel")), Gtk.ResponseType.CANCEL) self.but_cancel.set_image(common.IconFactory.get_image( 'tryton-cancel', Gtk.IconSize.BUTTON)) self.but_cancel.set_always_show_image(True) self.but_find = self.win.add_button( set_underline(_("Search")), Gtk.ResponseType.APPLY) self.but_find.set_image(common.IconFactory.get_image( 'tryton-search', Gtk.IconSize.BUTTON)) self.but_find.set_always_show_image(True) if new and common.MODELACCESS[model]['create']: self.but_new = self.win.add_button( set_underline(_("New")), Gtk.ResponseType.ACCEPT) self.but_new.set_image(common.IconFactory.get_image( 'tryton-create', Gtk.IconSize.BUTTON)) self.but_new.set_always_show_image(True) self.but_new.set_accel_path('/Form/New', self.accel_group) self.but_ok = self.win.add_button( set_underline(_("OK")), Gtk.ResponseType.OK) self.but_ok.set_image(common.IconFactory.get_image( 'tryton-ok', Gtk.IconSize.BUTTON)) self.but_ok.set_always_show_image(True) self.but_ok.add_accelerator( 'clicked', self.accel_group, Gdk.KEY_Return, Gdk.ModifierType.CONTROL_MASK, Gtk.AccelFlags.VISIBLE) hbox = Gtk.HBox() hbox.show() self.win.vbox.pack_start(hbox, expand=False, fill=True, padding=0) self.win.vbox.pack_start( Gtk.HSeparator(), expand=False, fill=True, padding=0) self.screen = Screen(model, domain=domain, mode=['tree'], order=order, context=context, view_ids=view_ids, views_preload=views_preload, row_activate=self.sig_activate, readonly=True) self.view = self.screen.current_view # Prevent to set tree_state self.screen.tree_states_done.add(id(self.view)) sel = self.view.treeview.get_selection() self.win.set_title(_('Search %s') % self.title) if not sel_multi: sel.set_mode(Gtk.SelectionMode.SINGLE) else: sel.set_mode(Gtk.SelectionMode.MULTIPLE) self.win.vbox.pack_start( self.screen.widget, expand=True, fill=True, padding=0) self.screen.widget.show() self.model_name = model self.register() def sig_activate(self, *args): self.view.treeview.stop_emission_by_name('row_activated') self.win.response(Gtk.ResponseType.OK) return True def destroy(self): self.screen.destroy() self.win.destroy() NoModal.destroy(self) def show(self): self.win.show() def hide(self): self.win.hide() def response(self, win, response_id): res = None if response_id == Gtk.ResponseType.OK: res = [r.id for r in self.screen.selected_records] elif response_id == Gtk.ResponseType.APPLY: self.screen.search_filter(self.screen.screen_container.get_text()) return elif response_id == Gtk.ResponseType.ACCEPT: # Remove first tree view as mode if form only view_ids = self.view_ids[1:] screen = Screen(self.model_name, domain=self.domain, context=self.context, order=self.order, mode=['form'], view_ids=view_ids, views_preload=self.views_preload, exclude_field=self.exclude_field, breadcrumb=[self.title]) def callback(result): if result: record = screen.current_record res = [(record.id, record.value.get('rec_name', ''))] self.callback(res) else: self.callback(None) self.destroy() WinForm(screen, callback, new=True, save_current=True) return if res: group = self.screen.group res = [(id_, group.get(id_).value.get('rec_name', '')) for id_ in res] self.callback(res) self.destroy() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685599.0 tryton-7.0.24/tryton/gui/window/window.py0000644000175000017500000000331014517761237016600 0ustar00cedced# This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. class Window(object): hide_current = False allow_similar = False def __init__(self, hide_current=False, allow_similar=True): Window.hide_current = hide_current Window.allow_similar = allow_similar def __enter__(self): return self def __exit__(self, type, value, traceback): Window.hide_current = False Window.allow_similar = False @staticmethod def create(model, **attributes): from tryton.gui import Main main = Main() if not Window.allow_similar: for other_page in main.pages: if other_page.compare(model, attributes): main.win_set(other_page) return if model: from .form import Form win = Form(model, **attributes) else: from .board import Board win = Board(model, **attributes) win.icon = attributes.get('icon') main.win_add(win, hide_current=Window.hide_current) @staticmethod def create_wizard(action, data, direct_print=False, email_print=False, email=None, name='', context=None, icon=None, window=False): from tryton.gui import Main from .wizard import WizardDialog, WizardForm if window: win = WizardForm(name=name) win.icon = icon Main().win_add(win, Window.hide_current) else: win = WizardDialog(name=name) win.run(action, data, direct_print=direct_print, email_print=email_print, email=email, context=context) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1725720314.0 tryton-7.0.24/tryton/gui/window/wizard.py0000644000175000017500000003512414667063372016602 0ustar00cedced# This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. import gettext import logging from gi.repository import Gdk, Gtk, Pango import tryton.common as common from tryton.common import ( TRYTON_ICON, RPCContextReload, RPCException, RPCExecute) from tryton.common.button import Button from tryton.common.widget_style import widget_class from tryton.exceptions import TrytonServerError from tryton.gui import Main from tryton.gui.window.nomodal import NoModal from tryton.gui.window.view_form.screen import Screen from .infobar import InfoBar from .tabcontent import TabContent _ = gettext.gettext logger = logging.getLogger(__name__) class Wizard(InfoBar): def __init__(self, name=''): super(Wizard, self).__init__() self.widget = Gtk.VBox(spacing=3) self.widget.show() self.name = name or _('Wizard') self.id = None self.ids = None self.action = None self.action_id = None self.direct_print = False self.email_print = False self.email = False self.context = None self.states = {} self.response2button = {} self.__processing = False self.__waiting_response = False self.session_id = None self.start_state = None self.end_state = None self.screen = None self.screen_state = None self.state = None def run(self, action, data, direct_print=False, email_print=False, email=None, context=None): self.action = action self.action_id = data.get('action_id') self.id = data.get('id') self.ids = data.get('ids') self.model = data.get('model') self.direct_print = direct_print self.email_print = email_print self.email = email self.context = context.copy() if context is not None else {} self.context['active_id'] = self.id self.context['active_ids'] = self.ids self.context['active_model'] = self.model self.context['action_id'] = self.action_id def callback(result): try: result = result() except RPCException: self.destroy() return self.session_id, self.start_state, self.end_state = result self.state = self.start_state self.process() RPCExecute('wizard', action, 'create', callback=callback) def process(self): from tryton.action import Action if self.__processing or self.__waiting_response: return self.__processing = True ctx = self.context.copy() if self.screen: data = { self.screen_state: self.screen.get_on_change_value(), } else: data = {} def callback(result): try: result = result() except RPCException as rpc_exception: if (not isinstance(rpc_exception.exception, TrytonServerError) or not self.screen): self.state = self.end_state self.end() self.__processing = False return if 'view' in result: self.clean() view = result['view'] self.update(view['fields_view'], view['buttons']) self.screen.new(default=False) if 'defaults' in view: self.screen.current_record.set_default(view['defaults']) if 'values' in view: self.screen.current_record.set(view['values']) self.update_buttons() self.screen.set_cursor() self.screen_state = view['state'] self.__waiting_response = True else: self.state = self.end_state def execute_actions(): for action in result.get('actions', []): for k, v in [ ('direct_print', self.direct_print), ('email_print', self.email_print), ('email', self.email), ]: action[0].setdefault(k, v) context = self.context.copy() # Remove wizard keys added by run del context['active_id'] del context['active_ids'] del context['active_model'] del context['action_id'] Action.execute(*action, context=context) if self.state == self.end_state: self.end(lambda *a: execute_actions()) else: execute_actions() self.__processing = False RPCExecute('wizard', self.action, 'execute', self.session_id, data, self.state, context=ctx, callback=callback) def destroy(self, action=None): if self.screen: self.screen.destroy() def end(self, callback=None): def end_callback(action): self.destroy(action=action()) if callback: callback() try: RPCExecute('wizard', self.action, 'delete', self.session_id, process_exception=False, callback=end_callback) except Exception: logger.warn( "Unable to delete session %s of wizard %s", self.session_id, self.action, exc_info=True) def clean(self): for widget in self.widget.get_children(): self.widget.remove(widget) self.states = {} def response(self, widget, response): self.__waiting_response = False button_attrs = self.response2button[response].attrs state = button_attrs.get('state', self.end_state) self.screen.current_view.set_value() if (button_attrs.get('validate', True) and not self.screen.current_record.validate()): self.screen.display(set_cursor=True) self.info_bar_add( self.screen.invalid_message(), Gtk.MessageType.ERROR) return self.info_bar_clear() self.state = state self.process() def _get_button(self, definition): button = Button(definition) self.states[definition['state']] = button response = len(self.states) self.response2button[response] = button button.show() return button def record_modified(self): self.update_buttons() self.info_bar_refresh() def update_buttons(self): record = self.screen.current_record for button in self.states.values(): button.state_set(record) def update(self, view, buttons): tooltips = common.Tooltips() for button in buttons: self._get_button(button) if self.screen: self.screen.windows.remove(self) self.screen = Screen(view['model'], mode=[], context=self.context) self.screen.add_view(view) self.screen.switch_view() self.screen.widget.show() self.screen.windows.append(self) title = Gtk.Label( label=common.ellipsize(self.name, 80), halign=Gtk.Align.START, margin=5, ellipsize=Pango.EllipsizeMode.END) tooltips.set_tip(title, self.name) title.set_size_request(0, -1) # Allow overflow title.show() hbox = Gtk.HBox() hbox.pack_start(title, expand=True, fill=True, padding=0) hbox.show() frame = Gtk.Frame() frame.set_shadow_type(Gtk.ShadowType.ETCHED_IN) widget_class(frame, 'wizard-title', True) frame.add(hbox) frame.show() self.widget.pack_start(frame, expand=False, fill=True, padding=3) viewport = Gtk.Viewport() viewport.set_shadow_type(Gtk.ShadowType.NONE) viewport.add(self.screen.widget) viewport.show() self.scrolledwindow = Gtk.ScrolledWindow() self.scrolledwindow.set_shadow_type(Gtk.ShadowType.NONE) self.scrolledwindow.set_policy( Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC) self.scrolledwindow.add(viewport) self.scrolledwindow.show() self.widget.pack_start( self.scrolledwindow, expand=True, fill=True, padding=0) self.widget.pack_start( self.create_info_bar(), expand=False, fill=True, padding=0) class WizardForm(Wizard, TabContent): "Wizard" def __init__(self, name=''): super(WizardForm, self).__init__(name=name) self.hbuttonbox = Gtk.HButtonBox() self.hbuttonbox.set_spacing(5) self.hbuttonbox.set_layout(Gtk.ButtonBoxStyle.END) self.hbuttonbox.show() self.dialogs = [] self.handlers = { 'but_close': self.sig_close } def clean(self): super(WizardForm, self).clean() for button in self.hbuttonbox.get_children(): self.hbuttonbox.remove(button) def _get_button(self, state): button = super(WizardForm, self)._get_button(state) response = len(self.states) button.connect('clicked', self.response, response) self.hbuttonbox.pack_start(button, expand=True, fill=True, padding=0) return button def update(self, view, buttons): super(WizardForm, self).update(view, buttons) self.widget.pack_start( self.hbuttonbox, expand=False, fill=True, padding=0) def sig_close(self): if self.end_state in self.states: self.states[self.end_state].clicked() return self.state == self.end_state def destroy(self, action=None): super(WizardForm, self).destroy(action=action) if action == 'reload menu': RPCContextReload(Main().sig_win_menu) elif action == 'reload context': RPCContextReload() def end(self, callback=None): super(WizardForm, self).end(callback=callback) Main()._win_del(self.widget) def set_cursor(self): if self.screen: self.screen.set_cursor(reset_view=False) class WizardDialog(Wizard, NoModal): def __init__(self, name=''): Wizard.__init__(self, name=name) NoModal.__init__(self) self.dia = Gtk.Dialog( title=self.name, transient_for=self.parent, destroy_with_parent=True) Main().add_window(self.dia) self.dia.set_position(Gtk.WindowPosition.CENTER_ON_PARENT) self.dia.set_icon(TRYTON_ICON) self.dia.set_deletable(False) self.dia.connect('delete-event', lambda *a: True) self.dia.connect('close', self.close) self.dia.connect('response', self.response) self.accel_group = Gtk.AccelGroup() self.dia.add_accel_group(self.accel_group) self._buttons = set() self.dia.vbox.pack_start( self.widget, expand=True, fill=True, padding=0) self.register() def clean(self): super(WizardDialog, self).clean() while self._buttons: button = self._buttons.pop() button.get_parent().remove(button) def _get_button(self, definition): button = super(WizardDialog, self)._get_button(definition) response = len(self.states) self.dia.add_action_widget(button, response) self._buttons.add(button) if definition['default']: button.add_accelerator( 'clicked', self.accel_group, Gdk.KEY_Return, Gdk.ModifierType.CONTROL_MASK, Gtk.AccelFlags.VISIBLE) button.get_style_context().add_class( Gtk.STYLE_CLASS_SUGGESTED_ACTION) button.set_can_default(True) button.grab_default() self.dia.set_default_response(response) return button def update(self, view, buttons): super(WizardDialog, self).update(view, buttons) current_view = self.screen.current_view self.scrolledwindow.set_policy( Gtk.PolicyType.NEVER, Gtk.PolicyType.NEVER) if current_view.scroll: current_view.scroll.set_policy( Gtk.PolicyType.NEVER, Gtk.PolicyType.NEVER) self.show() self.scrolledwindow.set_policy( Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC) if current_view.scroll: current_view.scroll.set_policy( Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC) def destroy(self, action=None): super(WizardDialog, self).destroy(action=action) self.dia.destroy() NoModal.destroy(self) main = Main() if self.parent == main.window: current_form = main.get_page() if current_form: for dialog in current_form.dialogs: dialog.show() if self.page.dialogs: dialog = self.page.dialogs[-1] else: dialog = self.page screen = getattr(dialog, 'screen', None) if self.sensible_widget == main.window: screen = main.menu_screen if screen: if (screen.current_record and self.sensible_widget != main.window): if screen.model_name == self.model: ids = self.ids else: # Wizard run from a children record so reload parent record ids = [screen.current_record.id] screen.reload(ids, written=True) if action: screen.client_action(action) def close(self, widget, event=None): widget.stop_emission_by_name('close') if self.end_state in self.states: self.states[self.end_state].clicked() return True def show(self): if not self.screen: return view = self.screen.current_view if view.view_type == 'form': expand = False for name in view.get_fields(): for widget in view.widgets[name]: if widget.expand: expand = True break if expand: break else: expand = True if expand: width, height = self.default_size() else: width, height = -1, -1 self.dia.set_default_size(width, height) # reshow with initial size self.dia.hide() self.dia.unrealize() self.dia.show() width, height = self.dia.get_size() screen = self.dia.get_screen() self.dia.resize( min(width, screen.width()), min(height, screen.height())) def hide(self): self.dia.hide() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1725720314.0 tryton-7.0.24/tryton/jsonrpc.py0000644000175000017500000003256014667063372014666 0ustar00cedced# This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. import base64 import datetime import errno import hashlib import http.client import json import logging import socket import ssl import threading import xmlrpc.client from collections import defaultdict from contextlib import contextmanager from decimal import Decimal from functools import partial, reduce from urllib.parse import quote, urljoin __all__ = ["ResponseError", "Fault", "ProtocolError", "Transport", "ServerProxy", "ServerPool"] CONNECT_TIMEOUT = 5 DEFAULT_TIMEOUT = None logger = logging.getLogger(__name__) def deepcopy(obj): """Recursively copy python mutable datastructures""" if isinstance(obj, (list, tuple)): return [deepcopy(o) for o in obj] elif isinstance(obj, dict): return {k: deepcopy(v) for k, v in obj.items()} else: return obj class ResponseError(xmlrpc.client.ResponseError): pass class Fault(xmlrpc.client.Fault): def __init__(self, faultCode, faultString='', **extra): super(Fault, self).__init__(faultCode, faultString, **extra) self.args = faultString def __str__(self): return str(self.faultCode) class ProtocolError(xmlrpc.client.ProtocolError): pass def object_hook(dct): if '__class__' in dct: if dct['__class__'] == 'datetime': return datetime.datetime(dct['year'], dct['month'], dct['day'], dct['hour'], dct['minute'], dct['second'], dct['microsecond']) elif dct['__class__'] == 'date': return datetime.date(dct['year'], dct['month'], dct['day']) elif dct['__class__'] == 'time': return datetime.time(dct['hour'], dct['minute'], dct['second'], dct['microsecond']) elif dct['__class__'] == 'timedelta': return datetime.timedelta(seconds=dct['seconds']) elif dct['__class__'] == 'bytes': return base64.decodebytes(dct['base64'].encode('utf-8')) elif dct['__class__'] == 'Decimal': return Decimal(dct['decimal']) return dct class JSONEncoder(json.JSONEncoder): def default(self, obj): if isinstance(obj, datetime.date): if isinstance(obj, datetime.datetime): return {'__class__': 'datetime', 'year': obj.year, 'month': obj.month, 'day': obj.day, 'hour': obj.hour, 'minute': obj.minute, 'second': obj.second, 'microsecond': obj.microsecond, } return {'__class__': 'date', 'year': obj.year, 'month': obj.month, 'day': obj.day, } elif isinstance(obj, datetime.time): return {'__class__': 'time', 'hour': obj.hour, 'minute': obj.minute, 'second': obj.second, 'microsecond': obj.microsecond, } elif isinstance(obj, datetime.timedelta): return {'__class__': 'timedelta', 'seconds': obj.total_seconds(), } elif isinstance(obj, bytes): return {'__class__': 'bytes', 'base64': base64.encodebytes(obj).decode('utf-8'), } elif isinstance(obj, Decimal): return {'__class__': 'Decimal', 'decimal': str(obj), } return super(JSONEncoder, self).default(obj) class JSONParser(object): def __init__(self, target): self.__targer = target def feed(self, data): self.__targer.feed(data) def close(self): pass class JSONUnmarshaller(object): def __init__(self): self.data = [] def feed(self, data): self.data.append(data.decode('utf-8')) def close(self): return json.loads(''.join(self.data), object_hook=object_hook) class Transport(xmlrpc.client.SafeTransport): accept_gzip_encoding = True def __init__( self, fingerprints=None, ca_certs=None, session=None): xmlrpc.client.Transport.__init__(self) self._connection = (None, None) self.__fingerprints = fingerprints self.__ca_certs = ca_certs self.session = session def getparser(self): target = JSONUnmarshaller() parser = JSONParser(target) return parser, target def parse_response(self, response): cache = None if hasattr(response, 'getheader'): cache = int(response.getheader('X-Tryton-Cache', 0)) response = super().parse_response(response) if cache: try: response['cache'] = int(cache) except ValueError: pass return response def get_host_info(self, host): host, extra_headers, x509 = xmlrpc.client.Transport.get_host_info( self, host) if extra_headers is None: extra_headers = [] if self.session: auth = base64.encodebytes( self.session.encode('utf-8')).decode('ascii') auth = ''.join(auth.split()) # get rid of whitespace extra_headers.append( ('Authorization', 'Session ' + auth), ) extra_headers.append(('Connection', 'keep-alive')) return host, extra_headers, x509 def send_headers(self, connection, headers): for key, val in headers: if key == 'Content-Type': val = 'application/json' connection.putheader(key, val) def make_connection(self, host): if self._connection and host == self._connection[0]: return self._connection[1] chost, self._extra_headers, x509 = self.get_host_info(host) ssl_ctx = ssl.create_default_context(cafile=self.__ca_certs) def http_connection(): self._connection = host, http.client.HTTPConnection(chost, timeout=CONNECT_TIMEOUT) self._connection[1].connect() sock = self._connection[1].sock sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) sock.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1) def https_connection(allow_http=False): self._connection = host, http.client.HTTPSConnection(chost, timeout=CONNECT_TIMEOUT, context=ssl_ctx) try: self._connection[1].connect() sock = self._connection[1].sock sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) sock.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1) try: peercert = sock.getpeercert(True) except socket.error: peercert = None def format_hash(value): return reduce(lambda x, y: x + y[1].upper() + ((y[0] % 2 and y[0] + 1 < len(value)) and ':' or ''), enumerate(value), '') return format_hash(hashlib.sha1(peercert).hexdigest()) except (socket.error, ssl.SSLError, ssl.CertificateError): if allow_http: http_connection() else: raise fingerprint = '' if (self.__fingerprints is not None and self.__fingerprints.exists(chost)): if self.__fingerprints.get(chost): fingerprint = https_connection() else: http_connection() else: fingerprint = https_connection(allow_http=True) if self.__fingerprints is not None: self.__fingerprints.set(chost, fingerprint) self._connection[1].timeout = DEFAULT_TIMEOUT self._connection[1].sock.settimeout(DEFAULT_TIMEOUT) return self._connection[1] @property def encode_threshold(self): if self.session: return 1400 # common MTU class ServerProxy(xmlrpc.client.ServerProxy): __id = 0 def __init__(self, host, port, database='', verbose=0, fingerprints=None, ca_certs=None, session=None, cache=None): self.__host = '%s:%s' % (host, port) if database: database = quote(database) self.__handler = '/%s/' % database else: self.__handler = '/' self.__transport = Transport(fingerprints, ca_certs, session) self.__verbose = verbose self.__cache = cache def __request(self, methodname, params): dumper = partial(json.dumps, cls=JSONEncoder, separators=(',', ':')) self.__id += 1 id_ = self.__id if self.__cache and self.__cache.cached(methodname): try: return self.__cache.get(methodname, dumper(params)) except KeyError: pass request = dumper({ 'id': id_, 'method': methodname, 'params': params, }).encode('utf-8') try: try: response = self.__transport.request( self.__host, self.__handler, request, verbose=self.__verbose ) except (socket.error, http.client.HTTPException) as v: if (isinstance(v, socket.error) and v.args[0] == errno.EPIPE): raise # try one more time self.__transport.close() response = self.__transport.request( self.__host, self.__handler, request, verbose=self.__verbose ) except xmlrpc.client.ProtocolError as e: raise Fault(str(e.errcode), e.errmsg) except Exception: self.__transport.close() raise if response['id'] != id_: raise ResponseError('Invalid response id (%s) excpected %s' % (response['id'], id_)) if response.get('error'): raise Fault(*response['error']) if self.__cache and response.get('cache'): self.__cache.set( methodname, dumper(params), response['cache'], response['result']) return response['result'] def close(self): self.__transport.close() @property def ssl(self): return isinstance(self.__transport.make_connection(self.__host), http.client.HTTPSConnection) @property def url(self): scheme = 'https' if self.ssl else 'http' return urljoin(scheme + '://' + self.__host, self.__handler) class ServerPool(object): keep_max = 4 _cache = None def __init__(self, host, port, database, *args, **kwargs): if kwargs.get('cache'): self._cache = kwargs['cache'] = _Cache() self.ServerProxy = partial( ServerProxy, host, port, database, *args, **kwargs) self._host = host self._port = port self._database = database self._lock = threading.Lock() self._pool = [] self._used = {} self.session = kwargs.get('session') def getconn(self): with self._lock: if self._pool: conn = self._pool.pop() else: conn = self.ServerProxy() self._used[id(conn)] = conn return conn def putconn(self, conn): with self._lock: self._pool.append(conn) del self._used[id(conn)] # Remove oldest connections while len(self._pool) > self.keep_max: conn = self._pool.pop() conn.close() def close(self): with self._lock: for conn in self._pool + list(self._used.values()): conn.close() self._pool = [] self._used.clear() @property def ssl(self): for conn in self._pool + list(self._used.values()): return conn.ssl return None @property def url(self): for conn in self._pool + list(self._used.values()): return conn.url @contextmanager def __call__(self): conn = self.getconn() yield conn self.putconn(conn) def clear_cache(self, prefix=None): if self._cache: self._cache.clear(prefix) class _Cache: def __init__(self): self.store = defaultdict(dict) def cached(self, prefix): return prefix in self.store def set(self, prefix, key, expire, value): if isinstance(expire, (int, float)): expire = datetime.timedelta(seconds=expire) if isinstance(expire, datetime.timedelta): expire = datetime.datetime.now() + expire self.store[prefix][key] = (expire, deepcopy(value)) def get(self, prefix, key): now = datetime.datetime.now() try: expire, value = self.store[prefix][key] except ValueError: raise KeyError if expire < now: self.store.pop(key) raise KeyError logger.info('(cached) %s %s', prefix, key) return deepcopy(value) def clear(self, prefix=None): if prefix: self.store[prefix].clear() else: self.store.clear() ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1745680285.2307782 tryton-7.0.24/tryton/plugins/0000755000175000017500000000000015003173635014276 5ustar00cedced././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1725720314.0 tryton-7.0.24/tryton/plugins/__init__.py0000644000175000017500000000265114667063372016426 0ustar00cedced# This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. import gettext import importlib import os from tryton.config import CURRENT_DIR, get_config_dir __all__ = ['MODULES', 'register'] _ = gettext.gettext MODULES = [] def register(): global MODULES paths = [ os.path.join(get_config_dir(), 'plugins'), os.path.join(CURRENT_DIR, 'plugins'), ] paths = list(filter(os.path.isdir, paths)) imported = set() for path in paths: finder = importlib.machinery.FileFinder( path, (importlib.machinery.SourceFileLoader, importlib.machinery.SOURCE_SUFFIXES), (importlib.machinery.SourcelessFileLoader, importlib.machinery.BYTECODE_SUFFIXES)) for plugin in os.listdir(path): module = os.path.splitext(plugin)[0] if (module.startswith('_') or module in imported): continue module = 'tryton.plugins.%s' % module spec = finder.find_spec(module) if not spec: continue module = importlib.util.module_from_spec(spec) try: spec.loader.exec_module(module) except ImportError: continue else: MODULES.append(module) imported.add(module.__name__) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1745680285.2307782 tryton-7.0.24/tryton/plugins/translation/0000755000175000017500000000000015003173635016634 5ustar00cedced././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685599.0 tryton-7.0.24/tryton/plugins/translation/__init__.py0000644000175000017500000000127114517761237020760 0ustar00cedced# This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. import gettext from tryton.common import MODELACCESS from tryton.gui.window import Window _ = gettext.gettext def translate_view(datas): model = datas['model'] Window.create('ir.translation', res_id=False, domain=[('model', '=', model)], mode=['tree', 'form'], name=_('Translate view')) def get_plugins(model): access = MODELACCESS['ir.translation'] if access['read'] and access['write']: return [ (_('Translate view'), translate_view), ] else: return [] ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1728935982.0 tryton-7.0.24/tryton/pyson.py0000644000175000017500000005313214703274056014350 0ustar00cedced# This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. import datetime import json from decimal import Decimal from functools import reduce from dateutil.relativedelta import relativedelta class PYSON(object): def pyson(self): raise NotImplementedError def types(self): raise NotImplementedError @staticmethod def eval(dct, context): raise NotImplementedError def __invert__(self): if self.types() != {bool}: return Not(Bool(self)) else: return Not(self) def __and__(self, other): if (isinstance(other, PYSON) and other.types() != {bool}): other = Bool(other) if (isinstance(self, And) and not isinstance(self, Or)): return And(*self._statements, other) if self.types() != {bool}: return And(Bool(self), other) else: return And(self, other) __rand__ = __and__ def __or__(self, other): if (isinstance(other, PYSON) and other.types() != {bool}): other = Bool(other) if isinstance(self, Or): return Or(*self._statements, other) if self.types() != {bool}: return Or(Bool(self), other) else: return Or(self, other) __ror__ = __or__ def __eq__(self, other): return Equal(self, other) def __ne__(self, other): return Not(Equal(self, other)) def __gt__(self, other): return Greater(self, other) def __ge__(self, other): return Greater(self, other, True) def __lt__(self, other): return Less(self, other) def __le__(self, other): return Less(self, other, True) def get(self, k, d=''): return Get(self, k, d) def in_(self, obj): return In(self, obj) def contains(self, k): return In(k, self) def __repr__(self): klass = self.__class__.__name__ return '%s(%s)' % (klass, ', '.join(map(repr, self.__repr_params__))) @property def __repr_params__(self): return NotImplementedError class PYSONEncoder(json.JSONEncoder): def default(self, obj): if isinstance(obj, PYSON): return obj.pyson() elif isinstance(obj, datetime.date): if isinstance(obj, datetime.datetime): return DateTime(obj.year, obj.month, obj.day, obj.hour, obj.minute, obj.second, obj.microsecond ).pyson() else: return Date(obj.year, obj.month, obj.day).pyson() elif isinstance(obj, datetime.timedelta): return TimeDelta(obj.days, obj.seconds, obj.microseconds) elif isinstance(obj, Decimal): return float(obj) return super(PYSONEncoder, self).default(obj) class PYSONDecoder(json.JSONDecoder): def __init__(self, context=None, noeval=False): self.__context = context or {} self.noeval = noeval super(PYSONDecoder, self).__init__(object_hook=self._object_hook) def _object_hook(self, dct): if '__class__' in dct: klass = CONTEXT.get(dct['__class__']) if klass: if not self.noeval: return klass.eval(dct, self.__context) else: dct = dct.copy() del dct['__class__'] return klass(**dct) return dct class Eval(PYSON): def __init__(self, v, d=''): super(Eval, self).__init__() self._value = v self._default = d @property def __repr_params__(self): return self._value, self._default def pyson(self): return { '__class__': 'Eval', 'v': self._value, 'd': self._default, } def types(self): if isinstance(self._default, PYSON): return self._default.types() else: return {type(self._default)} @staticmethod def eval(dct, context): if '.' in dct['v'] and dct['v'] not in context: base, name = dct['v'].split('.', 1) return Eval.eval({ 'v': name, 'd': dct['d'], }, context.get(base) or {}) return context.get(dct['v'], dct['d']) @property def basename(self): name = self._value if name.startswith('_parent_'): name = name[len('_parent_'):] if '.' in name: name = name.split('.', 1)[0] return name class Not(PYSON): def __init__(self, v): super(Not, self).__init__() if isinstance(v, PYSON): if v.types() != {bool}: v = Bool(v) elif not isinstance(v, bool): v = bool(v) self._value = v @property def __repr_params__(self): return (self._value,) def pyson(self): return { '__class__': 'Not', 'v': self._value, } def types(self): return {bool} @staticmethod def eval(dct, context): return not Bool(dct['v']).eval(dct, context) class Bool(PYSON): def __init__(self, v): super(Bool, self).__init__() self._value = v @property def __repr_params__(self): return (self._value,) def pyson(self): return { '__class__': 'Bool', 'v': self._value, } def types(self): return {bool} @staticmethod def eval(dct, context): return bool(dct['v']) class And(PYSON): def __init__(self, *statements, **kwargs): super(And, self).__init__() statements = list(statements) + kwargs.get('s', []) for i, statement in enumerate(list(statements)): if isinstance(statement, PYSON): if statement.types() != {bool}: statements[i] = Bool(statement) elif not isinstance(statement, bool): statements[i] = bool(statement) assert len(statements) >= 2, 'must have at least 2 statements' self._statements = statements @property def __repr_params__(self): return tuple(self._statements) def pyson(self): return { '__class__': 'And', 's': self._statements, } def types(self): return {bool} @staticmethod def eval(dct, context): return bool(reduce(lambda x, y: x and y, dct['s'])) class Or(And): def pyson(self): res = super(Or, self).pyson() res['__class__'] = 'Or' return res @staticmethod def eval(dct, context): return bool(reduce(lambda x, y: x or y, dct['s'])) class Equal(PYSON): def __init__(self, s1, s2): statement1, statement2 = s1, s2 super(Equal, self).__init__() if isinstance(statement1, PYSON): types1 = statement1.types() else: types1 = {type(s1)} if isinstance(statement2, PYSON): types2 = statement2.types() else: types2 = {type(s2)} assert types1 == types2, 'statements must have the same type' self._statement1 = statement1 self._statement2 = statement2 @property def __repr_params__(self): return (self._statement1, self._statement2) def pyson(self): return { '__class__': 'Equal', 's1': self._statement1, 's2': self._statement2, } def types(self): return {bool} @staticmethod def eval(dct, context): return dct['s1'] == dct['s2'] class Greater(PYSON): def __init__(self, s1, s2, e=False): statement1, statement2, equal = s1, s2, e super(Greater, self).__init__() for i in (statement1, statement2): if isinstance(i, PYSON): assert i.types().issubset({ int, float, type(None), datetime.datetime, datetime.date, datetime.timedelta}), \ 'statement must be an integer, float, date or datetime' else: assert isinstance(i, ( int, float, type(None), datetime.datetime, datetime.date, datetime.timedelta)), \ 'statement must be an integer, float, date or datetime' if isinstance(equal, PYSON): if equal.types() != {bool}: equal = Bool(equal) elif not isinstance(equal, bool): equal = bool(equal) self._statement1 = statement1 self._statement2 = statement2 self._equal = equal @property def __repr_params__(self): return (self._statement1, self._statement2, self._equal) def pyson(self): return { '__class__': 'Greater', 's1': self._statement1, 's2': self._statement2, 'e': self._equal, } def types(self): return {bool} @staticmethod def _convert(dct): for i in ('s1', 's2'): if dct[i] is None: dct[i] = 0.0 if not isinstance(dct[i], (int, float)): dct = dct.copy() stmt = dct[i] if isinstance(stmt, datetime.datetime): stmt = stmt.timestamp() elif isinstance(stmt, datetime.date): time = datetime.time(0, 0) stmt = datetime.datetime.combine(stmt, time).timestamp() elif isinstance(stmt, datetime.timedelta): stmt = stmt.total_seconds() dct[i] = float(stmt) return dct @staticmethod def eval(dct, context): if dct['s1'] is None or dct['s2'] is None: return False dct = Greater._convert(dct) if dct['e']: return dct['s1'] >= dct['s2'] else: return dct['s1'] > dct['s2'] class Less(Greater): def pyson(self): res = super(Less, self).pyson() res['__class__'] = 'Less' return res @staticmethod def eval(dct, context): if dct['s1'] is None or dct['s2'] is None: return False dct = Less._convert(dct) if dct['e']: return dct['s1'] <= dct['s2'] else: return dct['s1'] < dct['s2'] class If(PYSON): def __init__(self, c, t, e=None): condition, then_statement, else_statement = c, t, e super(If, self).__init__() if isinstance(condition, PYSON): if condition.types() != {bool}: condition = Bool(condition) elif not isinstance(condition, bool): condition = bool(condition) self._condition = condition self._then_statement = then_statement self._else_statement = else_statement @property def __repr_params__(self): return (self._condition, self._then_statement, self._else_statement) def pyson(self): return { '__class__': 'If', 'c': self._condition, 't': self._then_statement, 'e': self._else_statement, } def types(self): if isinstance(self._then_statement, PYSON): types = self._then_statement.types() else: types = {type(self._then_statement)} if isinstance(self._else_statement, PYSON): types |= self._else_statement.types() else: types |= {type(self._else_statement)} return types @staticmethod def eval(dct, context): if dct['c']: return dct['t'] else: return dct['e'] class Get(PYSON): def __init__(self, v, k, d=''): obj, key, default = v, k, d super(Get, self).__init__() if isinstance(obj, PYSON): assert obj.types() == {dict}, 'obj must be a dict' else: assert isinstance(obj, dict), 'obj must be a dict' self._obj = obj if isinstance(key, PYSON): assert key.types() == {str}, 'key must be a string' else: assert isinstance(key, str), 'key must be a string' self._key = key self._default = default @property def __repr_params__(self): return (self._obj, self._key, self._default) def pyson(self): return { '__class__': 'Get', 'v': self._obj, 'k': self._key, 'd': self._default, } def types(self): if isinstance(self._default, PYSON): return self._default.types() else: return {type(self._default)} @staticmethod def eval(dct, context): return dct['v'].get(dct['k'], dct['d']) class In(PYSON): def __init__(self, k, v): key, obj = k, v super(In, self).__init__() if isinstance(key, PYSON): assert key.types().issubset({str, int}), \ 'key must be a string or an integer or a long' else: assert isinstance(key, (str, int)), \ 'key must be a string or an integer or a long' if isinstance(obj, PYSON): assert obj.types().issubset({dict, list}), \ 'obj must be a dict or a list' if obj.types() == {dict}: if isinstance(key, PYSON): assert key.types() == {str}, 'key must be a string' else: assert isinstance(key, str), 'key must be a string' else: assert isinstance(obj, (dict, list)) if isinstance(obj, dict): if isinstance(key, PYSON): assert key.types() == {str}, 'key must be a string' else: assert isinstance(key, str), 'key must be a string' self._key = key self._obj = obj @property def __repr_params__(self): return (self._key, self._obj) def pyson(self): return { '__class__': 'In', 'k': self._key, 'v': self._obj, } def types(self): return {bool} @staticmethod def eval(dct, context): if dct['v']: return dct['k'] in dct['v'] else: return False class Date(PYSON): def __init__(self, year=None, month=None, day=None, delta_years=0, delta_months=0, delta_days=0, start=None, **kwargs): year = kwargs.get('y', year) month = kwargs.get('M', month) day = kwargs.get('d', day) delta_years = kwargs.get('dy', delta_years) delta_months = kwargs.get('dM', delta_months) delta_days = kwargs.get('dd', delta_days) super(Date, self).__init__() for i in (year, month, day, delta_years, delta_months, delta_days): if isinstance(i, PYSON): assert i.types().issubset({int, type(None)}), \ '%s must be an integer or None' % (i,) else: assert isinstance(i, (int, type(None))), \ '%s must be an integer or None' % (i,) self._year = year self._month = month self._day = day self._delta_years = delta_years self._delta_months = delta_months self._delta_days = delta_days self._start = start @property def __repr_params__(self): return (self._year, self._month, self._day, self._delta_years, self._delta_months, self._delta_days, self._start) def pyson(self): return { '__class__': 'Date', 'y': self._year, 'M': self._month, 'd': self._day, 'dy': self._delta_years, 'dM': self._delta_months, 'dd': self._delta_days, 'start': self._start, } def types(self): return {datetime.date} @staticmethod def eval(dct, context): today = dct.get('start') if isinstance(today, datetime.datetime): today = today.date() if not isinstance(today, datetime.date): today = datetime.date.today() return today + relativedelta( year=dct['y'], month=dct['M'], day=dct['d'], years=dct['dy'], months=dct['dM'], days=dct['dd'], ) class DateTime(Date): def __init__(self, year=None, month=None, day=None, hour=None, minute=None, second=None, microsecond=None, delta_years=0, delta_months=0, delta_days=0, delta_hours=0, delta_minutes=0, delta_seconds=0, delta_microseconds=0, start=None, **kwargs): hour = kwargs.get('h', hour) minute = kwargs.get('m', minute) second = kwargs.get('s', second) microsecond = kwargs.get('ms', microsecond) delta_hours = kwargs.get('dh', delta_hours) delta_minutes = kwargs.get('dm', delta_minutes) delta_seconds = kwargs.get('ds', delta_seconds) delta_microseconds = kwargs.get('dms', delta_microseconds) super(DateTime, self).__init__(year=year, month=month, day=day, delta_years=delta_years, delta_months=delta_months, delta_days=delta_days, start=start, **kwargs) for i in (hour, minute, second, microsecond, delta_hours, delta_minutes, delta_seconds, delta_microseconds): if isinstance(i, PYSON): assert i.types() == {int, type(None)}, \ '%s must be an integer or None' % (i,) else: assert isinstance(i, (int, type(None))), \ '%s must be an integer or None' % (i,) self._hour = hour self._minute = minute self._second = second self._microsecond = microsecond self._delta_hours = delta_hours self._delta_minutes = delta_minutes self._delta_seconds = delta_seconds self._delta_microseconds = delta_microseconds @property def __repr_params__(self): date_params = super(DateTime, self).__repr_params__ return (date_params[:3] + (self._hour, self._minute, self._second, self._microsecond) + date_params[3:-1] + (self._delta_hours, self._delta_minutes, self._delta_seconds, self._delta_microseconds) + date_params[-1:]) def pyson(self): res = super(DateTime, self).pyson() res['__class__'] = 'DateTime' res['h'] = self._hour res['m'] = self._minute res['s'] = self._second res['ms'] = self._microsecond res['dh'] = self._delta_hours res['dm'] = self._delta_minutes res['ds'] = self._delta_seconds res['dms'] = self._delta_microseconds return res def types(self): return {datetime.datetime} @staticmethod def eval(dct, context): now = dct.get('start') if (isinstance(now, datetime.date) and not isinstance(now, datetime.datetime)): now = datetime.datetime.combine(now, datetime.time()) if not isinstance(now, datetime.datetime): now = datetime.datetime.utcnow() return now + relativedelta( year=dct['y'], month=dct['M'], day=dct['d'], hour=dct['h'], minute=dct['m'], second=dct['s'], microsecond=dct['ms'], years=dct['dy'], months=dct['dM'], days=dct['dd'], hours=dct['dh'], minutes=dct['dm'], seconds=dct['ds'], microseconds=dct['dms'], ) class TimeDelta(PYSON): def __init__(self, days=0, seconds=0, microseconds=0): for i in [days, seconds, microseconds]: if isinstance(i, PYSON): assert i.types().issubset({int, float}), \ '%s must be an integer' % (i,) else: assert isinstance(i, (int, float)), \ '%s must be an integer' % (i,) self._days = days self._seconds = seconds self._microseconds = microseconds @property def __repr_params__(self): return self._days, self._seconds, self._microseconds def pyson(self): return { '__class__': 'TimeDelta', 'd': self._days, 's': self._seconds, 'm': self._microseconds, } def types(self): return {datetime.timedelta} @staticmethod def eval(dct, context): return datetime.timedelta( days=dct['d'], seconds=dct['s'], microseconds=dct['m'], ) class Len(PYSON): def __init__(self, v): super(Len, self).__init__() if isinstance(v, PYSON): assert v.types().issubset({dict, list, str}), \ 'value must be a dict or a list or a string' else: assert isinstance(v, (dict, list, str)), \ 'value must be a dict or list or a string' self._value = v @property def __repr_params__(self): return (self._value,) def pyson(self): return { '__class__': 'Len', 'v': self._value, } def types(self): return {int} @staticmethod def eval(dct, context): return len(dct['v']) CONTEXT = { 'Eval': Eval, 'Not': Not, 'Bool': Bool, 'And': And, 'Or': Or, 'Equal': Equal, 'Greater': Greater, 'Less': Less, 'If': If, 'Get': Get, 'In': In, 'Date': Date, 'DateTime': DateTime, 'TimeDelta': TimeDelta, 'Len': Len, 'true': True, 'false': False, } ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1707150267.0 tryton-7.0.24/tryton/rpc.py0000644000175000017500000001144714560205673013767 0ustar00cedced# This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. import http.client import logging import os import socket try: from http import HTTPStatus except ImportError: from http import client as HTTPStatus from functools import partial from tryton import bus, device_cookie, fingerprints from tryton.config import CONFIG, get_config_dir from tryton.exceptions import TrytonServerError, TrytonServerUnavailable from tryton.jsonrpc import Fault, ServerPool, ServerProxy logger = logging.getLogger(__name__) CONNECTION = None _USER = None CONTEXT = {} _VIEW_CACHE = {} _TOOLBAR_CACHE = {} _KEYWORD_CACHE = {} _CA_CERTS = os.path.join(get_config_dir(), 'ca_certs') if not os.path.isfile(_CA_CERTS): _CA_CERTS = None ServerProxy = partial(ServerProxy, fingerprints=fingerprints, ca_certs=_CA_CERTS) ServerPool = partial(ServerPool, fingerprints=fingerprints, ca_certs=_CA_CERTS) def context_reset(): CONTEXT.clear() CONTEXT['client'] = bus.ID context_reset() def db_list(host, port): try: connection = ServerProxy(host, port) logger.info('common.db.list()') result = connection.common.db.list() logger.debug('%r', result) return result except Fault as exception: logger.debug(exception.faultCode) if exception.faultCode == str(HTTPStatus.FORBIDDEN.value): return [] else: return None def server_version(host, port): try: connection = ServerProxy(host, port) logger.info('common.server.version(None, None)') result = connection.common.server.version() logger.debug('%r', result) return result except Exception as e: logger.exception(e) return None def authentication_services(host, port): try: connection = ServerProxy(host, port) logger.info('common.authentication.services()') services = connection.common.authentication.services() logger.debug('%r', services) return connection.url, services except Exception as e: logger.exception(e) return '', [] def set_service_session(parameters): from tryton import common global CONNECTION, _USER host = CONFIG['login.host'] hostname = common.get_hostname(host) port = common.get_port(host) database = CONFIG['login.db'] CONFIG['login.login'] = username = parameters.get('login', [''])[0] try: user_id = int(parameters.get('user_id', [None])[0]) except TypeError: pass session = parameters.get('session', [''])[0] if 'renew' in parameters: renew_id = int(parameters.get('renew', [-1])[0]) if _USER != renew_id: raise ValueError _USER = user_id session = ':'.join(map(str, [username, user_id, session])) if CONNECTION is not None: CONNECTION.close() CONNECTION = ServerPool( hostname, port, database, session=session, cache=not CONFIG['dev']) bus.listen(CONNECTION) def login(parameters): from tryton import common global CONNECTION, _USER host = CONFIG['login.host'] hostname = common.get_hostname(host) port = common.get_port(host) database = CONFIG['login.db'] username = CONFIG['login.login'] language = CONFIG['client.lang'] parameters['device_cookie'] = device_cookie.get() connection = ServerProxy(hostname, port, database) logger.info('common.db.login(%s, %s, %s)', username, 'x' * 10, language) result = connection.common.db.login(username, parameters, language) logger.debug('%r', result) _USER = result[0] session = ':'.join(map(str, [username] + result)) if CONNECTION is not None: CONNECTION.close() CONNECTION = ServerPool( hostname, port, database, session=session, cache=not CONFIG['dev']) device_cookie.renew() bus.listen(CONNECTION) def logout(): global CONNECTION, _USER if CONNECTION is not None: try: logger.info('common.db.logout()') with CONNECTION() as conn: conn.common.db.logout() except (Fault, socket.error, http.client.CannotSendRequest): pass CONNECTION.close() CONNECTION = None _USER = None def execute(*args): global CONNECTION, _USER if CONNECTION is None: raise TrytonServerError('403') try: name = '.'.join(args[:3]) args = args[3:] logger.info('%s%r', name, args) with CONNECTION() as conn: result = getattr(conn, name)(*args) except (http.client.CannotSendRequest, socket.error) as exception: raise TrytonServerUnavailable(*exception.args) logger.debug('%r', result) return result def clear_cache(prefix=None): if CONNECTION: CONNECTION.clear_cache(prefix) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1745680285.2341115 tryton-7.0.24/tryton/tests/0000755000175000017500000000000015003173635013757 5ustar00cedced././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685599.0 tryton-7.0.24/tryton/tests/__init__.py0000644000175000017500000000022014517761237016074 0ustar00cedced# This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685599.0 tryton-7.0.24/tryton/tests/test_common.py0000644000175000017500000000262414517761237016676 0ustar00cedced# This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. from unittest import TestCase from tryton.common import humanize class Humanize(TestCase): "Test humanize" def test_humanize(self): "Test humanize" for value, text in [ (0, '0'), (1, '1'), (5, '5'), (10, '10'), (50, '50'), (100, '100'), (1000, '1000'), (1001, '1k'), (1500, '1.5k'), (1000000, '1000k'), (1000001, '1M'), (1010000, '1.01M'), (10**33, '1000Q'), (0.1, '0.1'), (0.5, '0.5'), (0.01, '0.01'), (0.05, '0.05'), (0.001, '1m'), (0.0001, '0.1m'), (0.000001, '1µ'), (0.0000015, '1.5µ'), (0.00000105, '1.05µ'), (0.000001001, '1µ'), (10**-33, '0.001q'), ]: with self.subTest(value=value): self.assertEqual(humanize(value), text) if value: value *= -1 text = '-' + text with self.subTest(value=value): self.assertEqual(humanize(value), text) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1730237511.0 tryton-7.0.24/tryton/tests/test_common_domain_parser.py0000644000175000017500000012555014710252107021567 0ustar00cedced# This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. import datetime as dt from decimal import Decimal from unittest import TestCase from tryton.common import untimezoned_date from tryton.common.domain_parser import ( DomainParser, complete_value, convert_value, format_value, group_operator, likify, operatorize, parenthesize, quote, rlist, split_target_value, udlex) class DomainParserTestCase(TestCase): "Test common domain_parser" def test_group_operator(self): "Test group_operator" self.assertEqual( list(group_operator(iter(['a', '>', '=']))), ['a', '>=']) self.assertEqual( list(group_operator(iter(['>', '=', 'b']))), ['>=', 'b']) self.assertEqual( list(group_operator(iter(['a', '=', 'b']))), ['a', '=', 'b']) self.assertEqual( list(group_operator(iter(['a', '>', '=', 'b']))), ['a', '>=', 'b']) self.assertEqual( list(group_operator(iter(['a', '>', '=', '=']))), ['a', '>=', '=']) def test_likify(self): "Test likify" for value, result in [ ('', '%'), ('foo', '%foo%'), ('foo%', 'foo%'), ('foo_bar', 'foo_bar'), ('foo\\%', '%foo\\%%'), ('foo\\_bar', '%foo\\_bar%'), ]: with self.subTest(value=value): self.assertEqual(likify(value), result) def test_quote(self): "Test quote" self.assertEqual(quote('test'), 'test') self.assertEqual(quote('foo bar'), '"foo bar"') self.assertEqual(quote('"foo"'), '\\\"foo\\\"') self.assertEqual(quote('foo\\bar'), 'foo\\\\bar') def test_split_target_value(self): "Test split_target_value" field = { 'type': 'reference', 'selection': [ ('spam', 'Spam'), ('ham', 'Ham'), ('e', 'Eggs'), ] } for value, result in ( ('Spam', (None, 'Spam')), ('foo', (None, 'foo')), ('Spam,', ('spam', '')), ('Ham,bar', ('ham', 'bar')), ('Eggs,foo', ('e', 'foo')), ): self.assertEqual( split_target_value(field, value), result, msg="split_target_value(%r, %r)" % (field, value)) def test_convert_boolean(self): "Test convert boolean" field = { 'type': 'boolean', } for value, result in ( ('Y', True), ('yes', True), ('t', True), ('1', True), ('N', False), ('False', False), ('no', False), ('0', False), (None, None), ): self.assertEqual( convert_value(field, value), result, msg="convert_value(%r, %r)" % (field, value)) def test_convert_float(self): "Test convert float" field = { 'type': 'float', } for value, result in ( ('1', 1.0), ('1.5', 1.5), ('', None), ('test', None), (None, None), ): self.assertEqual( convert_value(field, value), result, msg="convert_value(%r, %r)" % (field, value)) def test_convert_float_factor(self): "Test convert float with factor" field = { 'type': 'float', 'factor': '100', } self.assertEqual(convert_value(field, '42'), 0.42) def test_convert_integer(self): "Test convert integer" field = { 'type': 'integer', } for value, result in ( ('1', 1), ('1.5', 1), ('', None), ('test', None), (None, None), ): self.assertEqual( convert_value(field, value), result, msg="convert_value(%r, %r)" % (field, value)) def test_convert_integer_factor(self): "Test convert integer with factor" field = { 'type': 'integer', 'factor': '2', } self.assertEqual(convert_value(field, '6'), 3) def test_convert_numeric(self): "Test convert numeric" field = { 'type': 'numeric', } for value, result in ( ('1', Decimal(1)), ('1.5', Decimal('1.5')), ('', None), ('test', None), (None, None), ): self.assertEqual( convert_value(field, value), result, msg="convert_value(%r, %r)" % (field, value)) def test_convert_numeric_factor(self): "Test convert numeric with factor" field = { 'type': 'numeric', 'factor': '5', } self.assertEqual(convert_value(field, '1'), Decimal('0.2')) def test_convert_selection(self): "Test convert selection" field = { 'type': 'selection', 'selection': [ ('male', 'Male'), ('female', 'Female'), ], } field_with_empty = field.copy() field_with_empty['selection'] = (field_with_empty['selection'] + [('', '')]) for value, result in ( ('Male', 'male'), ('male', 'male'), ('test', 'test'), (None, None), ('', ''), ): self.assertEqual( convert_value(field, value), result, msg="convert_value(%r, %r)" % (field, value)) self.assertEqual( convert_value(field_with_empty, value), result, msg="convert_value(%r, %r)" % (field_with_empty, value)) def test_convert_datetime(self): "Test convert datetime" field = { 'type': 'datetime', 'format': '"%H:%M:%S"', } for value, result in ( ('12/04/2002', untimezoned_date(dt.datetime(2002, 12, 4))), ('12/04/2002 12:30:00', untimezoned_date( dt.datetime(2002, 12, 4, 12, 30))), ('02/03/04', untimezoned_date(dt.datetime(2004, 2, 3))), ('02/03/04 05:06:07', untimezoned_date( dt.datetime(2004, 2, 3, 5, 6, 7))), ('test', None), (None, None), ): self.assertEqual( convert_value(field, value), result, msg="convert_value(%r, %r)" % (field, value)) def test_convert_date(self): "Test convert date" field = { 'type': 'date', } for value, result in ( ('12/04/2002', dt.date(2002, 12, 4)), ('test', None), (None, None), ): self.assertEqual( convert_value(field, value), result, msg="convert_value(%r, %r)" % (field, value)) def test_convert_time(self): "Test convert time" field = { 'type': 'time', 'format': '"%H:%M:%S"', } for value, result in ( ('12:30:00', dt.time(12, 30, 0)), ('test', None), (None, None), ): self.assertEqual( convert_value(field, value), result, msg="convert_value(%r, %r)" % (field, value)) def test_convert_timedelta(self): "Test convert timedelta" field = { 'type': 'timedelta', } for value, result in [ ('1d 2:00', dt.timedelta(days=1, hours=2)), ('foo', dt.timedelta()), (None, None), ]: self.assertEqual( convert_value(field, value), result, msg="convert_value(%r, %r)" % (field, value)) def test_format_boolean(self): "Test format boolean" field = { 'type': 'boolean', } for value, result in ( (True, 'True'), (False, 'False'), (None, ''), ): self.assertEqual( format_value(field, value), result, msg="format_value(%r, %r)" % (field, value)) def test_format_integer(self): "Test format integer" field = { 'type': 'integer', } for value, result in ( (1, '1'), (1.5, '1'), (0, '0'), (0.0, '0'), (False, ''), (None, ''), ): self.assertEqual( format_value(field, value), result, msg="format_value(%r, %r)" % (field, value)) def test_format_integer_factor(self): "Test format integer with factor" field = { 'type': 'integer', 'factor': '2', } self.assertEqual(format_value(field, 3), '6') def test_format_float(self): "Test format float" field = { 'type': 'float', } for value, result in ( (1, '1'), (1.5, '1.5'), (1.50, '1.5'), (150.79, '150.79'), (0, '0'), (0.0, '0'), (False, ''), (None, ''), (1e-12, '0.000000000001'), (1.0579e-10, '0.00000000010579'), ): self.assertEqual( format_value(field, value), result, msg="format_value(%r, %r)" % (field, value)) def test_format_float_factor(self): "Test format float with factor" field = { 'type': 'float', 'factor': '100', } self.assertEqual(format_value(field, 0.42), '42') def test_format_numeric(self): "Test format numeric" field = { 'type': 'numeric', } for value, result in ( (Decimal(1), '1'), (Decimal('1.5'), '1.5'), (Decimal('1.50'), '1.5'), (Decimal('150.79'), '150.79'), (Decimal(0), '0'), (Decimal('0.0'), '0'), (False, ''), (None, ''), ): self.assertEqual( format_value(field, value), result, msg="format_value(%r, %r)" % (field, value)) def test_format_numeric_factor(self): "Test format numeric with factor" field = { 'type': 'numeric', 'factor': '5', } self.assertEqual(format_value(field, Decimal('0.2')), '1') def test_format_selection(self): "Test format selection" field = { 'type': 'selection', 'selection': [ ('male', 'Male'), ('female', 'Female'), ], } field_with_empty = field.copy() field_with_empty['selection'] = (field_with_empty['selection'] + [('', '')]) for value, result in ( ('male', 'Male'), ('test', 'test'), (False, ''), (None, ''), ('', ''), ): self.assertEqual( format_value(field, value), result, msg="format_value(%r, %r)" % (field, value)) self.assertEqual( format_value(field_with_empty, value), result, msg="format_value(%r, %r)" % (field_with_empty, value)) def test_format_datetime(self): "Test format datetime" field = { 'type': 'datetime', 'format': '"%H:%M:%S"', } for value, result in ( (dt.date(2002, 12, 4), dt.date(2002, 12, 4).strftime('%x')), (untimezoned_date(dt.datetime(2002, 12, 4)), dt.date(2002, 12, 4).strftime('%x')), (untimezoned_date(dt.datetime(2002, 12, 4, 12, 30)), dt.datetime(2002, 12, 4, 12, 30).strftime( '"%x %H:%M:%S"')), (False, ''), (None, ''), ): self.assertEqual( format_value(field, value), result, msg="format_value(%r, %r)" % (field, value)) def test_format_date(self): "Test format date" field = { 'type': 'date', } for value, result in ( (dt.date(2002, 12, 4), dt.date(2002, 12, 4).strftime('%x')), (False, ''), (None, ''), ): self.assertEqual( format_value(field, value), result, msg="format_value(%r, %r)" % (field, value)) def test_format_time(self): "Test format time" field = { 'type': 'time', 'format': '"%H:%M:%S"', } for value, result in ( (dt.time(12, 30, 0), '"12:30:00"'), (False, ''), (None, ''), ): self.assertEqual( format_value(field, value), result, msg="format_value(%r, %r)" % (field, value)) def test_format_timedelta(self): "Test format timedelta" field = { 'type': 'timedelta', } for value, result in [ (dt.timedelta(days=1, hours=2), '"1d 02:00"'), (dt.timedelta(), ''), (None, ''), ('', ''), ]: self.assertEqual( format_value(field, value), result, msg="format_value(%r, %r)" % (field, value)) def test_complete_boolean(self): "Test complete boolean" field = { 'type': 'boolean', } for value, result in [ (None, [True, False]), (True, [False]), (False, [True]), ]: self.assertEqual( list(complete_value(field, value)), result, msg="complete_value(%r, %r)" % (field, value)) def test_complete_selection(self): "Test complete selection" field = { 'type': 'selection', 'selection': [ ('male', 'Male'), ('female', 'Female'), ], } for value, result in ( ('m', ['male']), ('test', []), ('', ['male', 'female']), (None, ['male', 'female']), (['male', 'f'], [['male', 'female']]), (['male', None], [['male', 'male'], ['male', 'female']]), ): self.assertEqual( list(complete_value(field, value)), result, msg="complete_value(%r, %r)" % (field, value)) field_with_empty = field.copy() field_with_empty['selection'] = (field_with_empty['selection'] + [('', '')]) for value, result in ( ('m', ['male']), ('test', []), ('', ['male', 'female', '']), (None, ['male', 'female', '']), (['male', 'f'], [['male', 'female']]), ): self.assertEqual( list(complete_value(field_with_empty, value)), result, msg="complete_value(%r, %r)" % (field_with_empty, value)) def test_complete_reference(self): "Test complete reference" field = { 'type': 'reference', 'selection': [ ('spam', 'Spam'), ('ham', 'Ham'), ('', ''), ], } for value, result in ( ('s', ['%spam%']), ('test', []), ('', ['%spam%', '%ham%', '%']), (None, ['%spam%', '%ham%', '%']), (['spam', 'h'], [['spam', 'ham']]), ): self.assertEqual( list(complete_value(field, value)), result, msg="complete_value(%r, %r)" % (field, value)) def test_parenthesize(self): for value, result in ( (['a'], ['a']), (['a', 'b'], ['a', 'b']), (['(', 'a', ')'], [['a']]), (['a', 'b', '(', 'c', '(', 'd', 'e', ')', 'f', ')', 'g'], ['a', 'b', ['c', ['d', 'e'], 'f'], 'g']), (['a', 'b', '(', 'c'], ['a', 'b', ['c']]), (['a', 'b', '(', 'c', '(', 'd', 'e', ')', 'f'], ['a', 'b', ['c', ['d', 'e'], 'f']]), (['a', 'b', ')'], ['a', 'b']), (['a', 'b', ')', 'c', ')', 'd)'], ['a', 'b']), ): self.assertEqual( rlist(parenthesize(iter(value))), result, msg="parenthesize(%r)" % value) def test_operatorize(self): "Test operatorize" a = ('a', 'a', 'a') b = ('b', 'b', 'b') c = ('c', 'c', 'c') null_ = ('d', None, 'x') double_null_ = ('e', None, None) for value, result in ( (['a'], ['a']), (['a', '|', 'b'], [['OR', 'a', 'b']]), (['a', '|', 'b', '|', 'c'], [['OR', ['OR', 'a', 'b'], 'c']]), (['a', 'b', '|', 'c'], ['a', ['OR', 'b', 'c']]), (['a', '|', 'b', 'c'], [['OR', 'a', 'b'], 'c']), (['a', iter(['b', 'c'])], ['a', ['b', 'c']]), (['a', iter(['b', 'c']), 'd'], ['a', ['b', 'c'], 'd']), (['a', '|', iter(['b', 'c'])], [['OR', 'a', ['b', 'c']]]), (['a', '|', iter(['b', 'c']), 'd'], [['OR', 'a', ['b', 'c']], 'd']), (['a', iter(['b', 'c']), '|', 'd'], ['a', ['OR', ['b', 'c'], 'd']]), (['a', '|', iter(['b', '|', 'c'])], [['OR', 'a', [['OR', 'b', 'c']]]]), (['|'], []), (['|', 'a'], ['a']), (['a', iter(['|', 'b'])], ['a', ['b']]), (['a', '|', '|', 'b'], [['OR', 'a', 'b']]), (['|', '|', 'a'], ['a']), (['|', '|', 'a', 'b'], ['a', 'b']), (['|', '|', 'a', '|', 'b'], [['OR', 'a', 'b']]), (['a', iter(['b', '|', 'c'])], ['a', [['OR', 'b', 'c']]]), ([a, iter([b, ('|',), c])], [a, [['OR', b, c]]]), (['a', iter(['b', '|'])], ['a', [['OR', 'b']]]), ([null_], [null_]), ([null_, '|', double_null_], [['OR', null_, double_null_]]), ): self.assertEqual( rlist(operatorize(iter(value))), result, msg="operatorize(%r)" % value) def test_stringable(self): "Test stringable" dom = DomainParser({ 'name': { 'string': 'Name', 'type': 'char', }, 'multiselection': { 'string': "MultiSelection", 'type': 'multiselection', 'selection': [ ('foo', "Foo"), ('bar', "Bar"), ('baz', "Baz"), ], }, 'relation': { 'string': 'Relation', 'type': 'many2one', 'relation_fields': { 'name': { 'string': "Name", 'type': 'char', }, }, }, 'relations': { 'string': 'Relations', 'type': 'many2many', }, }) valid = ('name', '=', 'Doe') invalid = ('surname', '=', 'John') self.assertTrue(dom.stringable([])) self.assertTrue(dom.stringable([[]])) self.assertTrue(dom.stringable([valid])) self.assertFalse(dom.stringable([invalid])) self.assertTrue(dom.stringable(['AND', valid])) self.assertFalse(dom.stringable(['AND', valid, invalid])) self.assertTrue(dom.stringable([[valid]])) self.assertFalse(dom.stringable([[valid], [invalid]])) self.assertTrue(dom.stringable([('multiselection', '=', None)])) self.assertTrue(dom.stringable([('multiselection', '=', '')])) self.assertFalse(dom.stringable([('multiselection', '=', 'foo')])) self.assertTrue(dom.stringable([('multiselection', '=', ['foo'])])) self.assertTrue(dom.stringable([('relation', '=', None)])) self.assertTrue(dom.stringable([('relation', '=', "Foo")])) self.assertTrue(dom.stringable([('relation.rec_name', '=', "Foo")])) self.assertFalse(dom.stringable([('relation', '=', 1)])) self.assertTrue(dom.stringable([('relations', '=', "Foo")])) self.assertTrue(dom.stringable([('relations', '=', None)])) self.assertTrue(dom.stringable([('relations', 'in', ["Foo"])])) self.assertFalse(dom.stringable([('relations', 'in', [42])])) self.assertTrue(dom.stringable([('relation.name', '=', "Foo")])) def test_string(self): dom = DomainParser({ 'name': { 'string': 'Name', 'type': 'char', }, 'surname': { 'string': '(Sur)Name', 'type': 'char', }, 'date': { 'string': 'Date', 'type': 'date', }, 'selection': { 'string': 'Selection', 'type': 'selection', 'selection': [ ('male', 'Male'), ('female', 'Female'), ('', ''), ], }, 'multiselection': { 'string': "MultiSelection", 'type': 'multiselection', 'selection': [ ('foo', "Foo"), ('bar', "Bar"), ('baz', "Baz"), ], }, 'reference': { 'string': 'Reference', 'type': 'reference', 'selection': [ ('spam', 'Spam'), ('ham', 'Ham'), ] }, 'many2one': { 'string': 'Many2One', 'name': 'many2one', 'type': 'many2one', 'relation_fields': { 'name': { 'string': "Name", 'type': 'char', }, }, }, }) self.assertEqual(dom.string([('name', '=', 'Doe')]), 'Name: =Doe') self.assertEqual(dom.string([('name', '=', None)]), 'Name: =') self.assertEqual(dom.string([('name', '=', '')]), 'Name: =""') self.assertEqual(dom.string([('name', 'ilike', '%')]), 'Name: ') self.assertEqual(dom.string([('name', 'ilike', '%Doe%')]), 'Name: Doe') self.assertEqual( dom.string([('name', 'ilike', '%<%')]), 'Name: "" "<"') self.assertEqual(dom.string([('name', 'ilike', 'Doe')]), 'Name: =Doe') self.assertEqual(dom.string([('name', 'ilike', 'Doe%')]), 'Name: Doe%') self.assertEqual( dom.string([('name', 'ilike', 'Doe\\%')]), 'Name: =Doe%') self.assertEqual( dom.string([('name', 'not ilike', '%Doe%')]), 'Name: !Doe') self.assertEqual( dom.string([('name', 'in', ['John', 'Jane'])]), 'Name: John;Jane') self.assertEqual( dom.string([('name', 'in', ['John', ''])]), 'Name: John;""') self.assertEqual( dom.string([('name', 'in', ['John'])]), 'Name: =John') self.assertEqual( dom.string([('name', 'not in', ['John', 'Jane'])]), 'Name: !John;Jane') self.assertEqual( dom.string([('name', 'not in', ['John'])]), 'Name: !=John') self.assertEqual( dom.string([ ('name', 'ilike', '%Doe%'), ('name', 'ilike', '%Jane%')]), 'Name: Doe Name: Jane') self.assertEqual( dom.string(['AND', ('name', 'ilike', '%Doe%'), ('name', 'ilike', '%Jane%')]), 'Name: Doe Name: Jane') self.assertEqual( dom.string(['OR', ('name', 'ilike', '%Doe%'), ('name', 'ilike', '%Jane%')]), 'Name: Doe | Name: Jane') self.assertEqual( dom.string([ ('name', 'ilike', '%Doe%'), ['OR', ('name', 'ilike', '%John%'), ('name', 'ilike', '%Jane%')]]), 'Name: Doe (Name: John | Name: Jane)') self.assertEqual(dom.string([]), '') self.assertEqual( dom.string([('surname', 'ilike', '%Doe%')]), '"(Sur)Name": Doe') self.assertEqual( dom.string([('date', '>=', dt.date(2012, 10, 24))]), dt.date(2012, 10, 24).strftime('Date: >=%x')) self.assertEqual(dom.string([('selection', '=', '')]), 'Selection: ') self.assertEqual(dom.string([('selection', '=', None)]), 'Selection: ') self.assertEqual( dom.string([('selection', '!=', '')]), 'Selection: !""') self.assertEqual( dom.string([('selection', '=', 'male')]), 'Selection: Male') self.assertEqual( dom.string([('selection', '!=', 'male')]), 'Selection: !Male') self.assertEqual( dom.string([('multiselection', '=', None)]), "MultiSelection: =") self.assertEqual( dom.string([('multiselection', '=', '')]), "MultiSelection: =") self.assertEqual( dom.string([('multiselection', '!=', '')]), "MultiSelection: !=") self.assertEqual( dom.string([('multiselection', '=', ['foo'])]), "MultiSelection: =Foo") self.assertEqual( dom.string([('multiselection', '!=', ['foo'])]), "MultiSelection: !=Foo") self.assertEqual( dom.string([('multiselection', '=', ['foo', 'bar'])]), "MultiSelection: =Foo;Bar") self.assertEqual( dom.string([('multiselection', '!=', ['foo', 'bar'])]), "MultiSelection: !=Foo;Bar") self.assertEqual( dom.string([('multiselection', 'in', ['foo'])]), "MultiSelection: Foo") self.assertEqual( dom.string([('multiselection', 'not in', ['foo'])]), "MultiSelection: !Foo") self.assertEqual( dom.string([('multiselection', '=', ['foo', 'bar'])]), "MultiSelection: =Foo;Bar") self.assertEqual( dom.string([('multiselection', '!=', ['foo', 'bar'])]), "MultiSelection: !=Foo;Bar") self.assertEqual( dom.string([('multiselection', 'in', ['foo', 'bar'])]), "MultiSelection: Foo;Bar") self.assertEqual( dom.string([('multiselection', 'not in', ['foo', 'bar'])]), "MultiSelection: !Foo;Bar") self.assertEqual( dom.string([('reference', 'ilike', '%foo%')]), 'Reference: foo') self.assertEqual( dom.string([('reference.rec_name', 'ilike', '%bar%', 'spam')]), 'Reference: Spam,bar') self.assertEqual( dom.string([('reference', 'in', ['foo', 'bar'])]), 'Reference: foo;bar') self.assertEqual( dom.string([('many2one', 'ilike', '%John%')]), 'Many2One: John') self.assertEqual( dom.string([('many2one.rec_name', 'in', ['John', 'Jane'])]), 'Many2One: John;Jane') self.assertEqual( dom.string([('many2one.name', 'ilike', '%Foo%')]), "Many2One.Name: Foo") def test_group(self): "Test group" dom = DomainParser({ 'name': { 'string': 'Name', }, 'firstname': { 'string': 'First Name', }, 'surname': { 'string': '(Sur)Name', }, 'relation': { 'string': "Relation", 'relation': 'relation', 'relation_fields': { 'name': { 'string': "Name", }, }, }, }) self.assertEqual( rlist(dom.group(udlex('Name: Doe'))), [('Name', None, 'Doe')]) self.assertEqual( rlist(dom.group(udlex('"(Sur)Name": Doe'))), [ ('(Sur)Name', None, 'Doe'), ]) self.assertEqual( rlist(dom.group(udlex('Name: Doe Name: John'))), [ ('Name', None, 'Doe'), ('Name', None, 'John'), ]) self.assertEqual( rlist(dom.group(udlex('Name: Name: John'))), [ ('Name', None, None), ('Name', None, 'John')]) self.assertEqual( rlist(dom.group(udlex('First Name: John'))), [ ('First Name', None, 'John'), ]) self.assertEqual( rlist(dom.group(udlex('Name: Doe First Name: John'))), [ ('Name', None, 'Doe'), ('First Name', None, 'John'), ]) self.assertEqual( rlist(dom.group(udlex('First Name: John Name: Doe'))), [ ('First Name', None, 'John'), ('Name', None, 'Doe'), ]) self.assertEqual( rlist(dom.group(udlex('First Name: John First Name: Jane'))), [ ('First Name', None, 'John'), ('First Name', None, 'Jane'), ]) self.assertEqual( rlist(dom.group(udlex('Name: John Doe'))), [ ('Name', None, 'John'), ('Doe',), ]) self.assertEqual( rlist(dom.group(udlex('Name: "John Doe"'))), [ ('Name', None, 'John Doe'), ]) self.assertEqual( rlist(dom.group(udlex('Doe Name: John'))), [ ('Doe',), ('Name', None, 'John'), ]) self.assertEqual( rlist(dom.group(udlex('Name: =Doe'))), [('Name', '=', 'Doe')]) self.assertEqual( rlist(dom.group(udlex('Name: =Doe Name: >John'))), [ ('Name', '=', 'Doe'), ('Name', '>', 'John'), ]) self.assertEqual( rlist(dom.group(udlex('First Name: =John First Name: =Jane'))), [ ('First Name', '=', 'John'), ('First Name', '=', 'Jane'), ]) self.assertEqual( rlist(dom.group(udlex('Name: John;Jane'))), [ ('Name', None, ['John', 'Jane']) ]) self.assertEqual( rlist(dom.group(udlex('Name: John;'))), [ ('Name', None, ['John']) ]) self.assertEqual( rlist(dom.group(udlex('Name: John;Jane Name: Doe'))), [ ('Name', None, ['John', 'Jane']), ('Name', None, 'Doe'), ]) self.assertEqual( rlist(dom.group(udlex('Name: John; Name: Doe'))), [ ('Name', None, ['John']), ('Name', None, 'Doe'), ]) self.assertEqual( rlist(dom.group(udlex('Name:'))), [ ('Name', None, None), ]) self.assertEqual( rlist(dom.group(udlex('Name: ='))), [ ('Name', '=', None), ]) self.assertEqual( rlist(dom.group(udlex('Name: =""'))), [ ('Name', '=', ''), ]) self.assertEqual( rlist(dom.group(udlex('Name: = ""'))), [ ('Name', '=', ''), ]) self.assertEqual( rlist(dom.group(udlex('Name: = Name: Doe'))), [ ('Name', '=', None), ('Name', None, 'Doe'), ]) self.assertEqual( rlist(dom.group(udlex('Name: \\"foo\\"'))), [ ('Name', None, '"foo"'), ]) self.assertEqual( rlist(dom.group(udlex('Name: "" <'))), [ ('Name', '', '<'), ]) self.assertEqual( rlist(dom.group(udlex('Relation.Name: Test'))), [ ('Relation.Name', None, "Test"), ]) def test_parse_clause(self): "Test parse clause" dom = DomainParser({ 'name': { 'string': 'Name', 'name': 'name', 'type': 'char', }, 'integer': { 'string': 'Integer', 'name': 'integer', 'type': 'integer', }, 'selection': { 'string': 'Selection', 'name': 'selection', 'type': 'selection', 'selection': [ ('male', 'Male'), ('female', 'Female'), ], }, 'multiselection': { 'string': "MultiSelection", 'name': 'multiselection', 'type': 'multiselection', 'selection': [ ('foo', "Foo"), ('bar', "Bar"), ('baz', "Baz"), ], }, 'reference': { 'string': 'Reference', 'name': 'reference', 'type': 'reference', 'selection': [ ('spam', 'Spam'), ('ham', 'Ham'), ] }, 'many2one': { 'string': 'Many2One', 'name': 'many2one', 'type': 'many2one', }, 'relation': { 'string': "Relation", 'relation': 'relation', 'name': 'relation', 'relation_fields': { 'name': { 'string': "Name", 'name': 'name', 'type': 'char', }, }, }, }) self.assertEqual( rlist(dom.parse_clause([('John',)])), [ ('rec_name', 'ilike', '%John%'), ]) self.assertEqual( rlist(dom.parse_clause([('Name', None, None)])), [ ('name', 'ilike', '%'), ]) self.assertEqual( rlist(dom.parse_clause([('Name', '', None)])), [ ('name', 'ilike', '%'), ]) self.assertEqual( rlist(dom.parse_clause([('Name', '=', None)])), [ ('name', '=', None), ]) self.assertEqual( rlist(dom.parse_clause([('Name', '=', '')])), [ ('name', '=', ''), ]) self.assertEqual( rlist(dom.parse_clause([('Name', None, 'Doe')])), [ ('name', 'ilike', '%Doe%'), ]) self.assertEqual( rlist(dom.parse_clause([('Name', '!', 'Doe')])), [ ('name', 'not ilike', '%Doe%'), ]) self.assertEqual( rlist(dom.parse_clause([('Name', None, ['John', 'Jane'])])), [ ('name', 'in', ['John', 'Jane']), ]) self.assertEqual( rlist(dom.parse_clause([('Name', '!', ['John', 'Jane'])])), [ ('name', 'not in', ['John', 'Jane']), ]) self.assertEqual( rlist(dom.parse_clause([('Selection', None, None)])), [ ('selection', '=', None), ]) self.assertEqual( rlist(dom.parse_clause([('Selection', None, '')])), [ ('selection', '=', ''), ]) self.assertEqual( rlist(dom.parse_clause([('Selection', None, ['Male', 'Female'])])), [('selection', 'in', ['male', 'female'])]) self.assertEqual( rlist(dom.parse_clause([('MultiSelection', None, None)])), [ ('multiselection', '=', None), ]) self.assertEqual( rlist(dom.parse_clause([('MultiSelection', None, '')])), [ ('multiselection', 'in', ['']), ]) self.assertEqual( rlist(dom.parse_clause([('MultiSelection', '=', '')])), [ ('multiselection', '=', ['']), ]) self.assertEqual( rlist(dom.parse_clause([('MultiSelection', '!', '')])), [ ('multiselection', 'not in', ['']), ]) self.assertEqual( rlist(dom.parse_clause([('MultiSelection', '!=', '')])), [ ('multiselection', '!=', ['']), ]) self.assertEqual( rlist(dom.parse_clause( [('MultiSelection', None, ['Foo', 'Bar'])])), [ ('multiselection', 'in', ['foo', 'bar']), ]) self.assertEqual( rlist(dom.parse_clause( [('MultiSelection', '=', ['Foo', 'Bar'])])), [ ('multiselection', '=', ['foo', 'bar']), ]) self.assertEqual( rlist(dom.parse_clause( [('MultiSelection', '!', ['Foo', 'Bar'])])), [ ('multiselection', 'not in', ['foo', 'bar']), ]) self.assertEqual( rlist(dom.parse_clause( [('MultiSelection', '!=', ['Foo', 'Bar'])])), [ ('multiselection', '!=', ['foo', 'bar']), ]) self.assertEqual( rlist(dom.parse_clause([('Integer', None, None)])), [ ('integer', '=', None), ]) self.assertEqual( rlist(dom.parse_clause([('Integer', None, '3..5')])), [[ ('integer', '>=', 3), ('integer', '<=', 5), ]]) self.assertEqual( rlist(dom.parse_clause([('Reference', None, 'foo')])), [ ('reference', 'ilike', '%foo%'), ]) self.assertEqual( rlist(dom.parse_clause([('Reference', None, 'Spam')])), [ ('reference', 'ilike', '%spam%'), ]) self.assertEqual( rlist(dom.parse_clause([('Reference', None, 'Spam,bar')])), [ ('reference.rec_name', 'ilike', '%bar%', 'spam'), ]) self.assertEqual( rlist(dom.parse_clause([('Reference', None, ['foo', 'bar'])])), [ ('reference', 'in', ['foo', 'bar']), ]) self.assertEqual( rlist(dom.parse_clause(['OR', ('Name', None, 'John'), ('Name', None, 'Jane')])), ['OR', ('name', 'ilike', '%John%'), ('name', 'ilike', '%Jane%'), ]) self.assertEqual( rlist(dom.parse_clause([('Many2One', None, 'John')])), [ ('many2one', 'ilike', '%John%'), ]) self.assertEqual( rlist(dom.parse_clause([('Many2One', None, ['John', 'Jane'])])), [ ('many2one.rec_name', 'in', ['John', 'Jane']), ]) self.assertEqual( rlist(dom.parse_clause(iter([iter([['John']])]))), [ [('rec_name', 'ilike', '%John%')]]) self.assertEqual( rlist(dom.parse_clause(iter([['Relation.Name', None, "Test"]]))), [('relation.name', 'ilike', "%Test%")]) self.assertEqual( rlist(dom.parse_clause(iter([['OR']]))), [('rec_name', 'ilike', "%OR%")]) self.assertEqual( rlist(dom.parse_clause(iter([['AND']]))), [('rec_name', 'ilike', "%AND%")]) def test_completion_char(self): "Test completion char" dom = DomainParser({ 'name': { 'string': 'Name', 'name': 'name', 'type': 'char', }, }) self.assertEqual(list(dom.completion('Nam')), ['Name: ']) self.assertEqual(list(dom.completion('Name:')), ['Name: ']) self.assertEqual(list(dom.completion('Name: foo')), []) self.assertEqual(list(dom.completion('Name: !=')), []) self.assertEqual(list(dom.completion('Name: !=foo')), []) self.assertEqual(list(dom.completion('')), ['Name: ']) self.assertEqual(list(dom.completion(' ')), ['', 'Name: ']) self.assertEqual(list(dom.completion('Name: foo |')), ['Name: foo']) self.assertEqual( list(dom.completion('Name: foo (Name: foo | N')), ['Name: foo (Name: foo | Name: ']) def test_completion_many2one(self): "Test completion many2one" dom = DomainParser({ 'relation': { 'name': 'relation', 'string': "Relation", 'type': 'many2one', 'relation_fields': { 'name': { 'name': 'name', 'string': "Name", 'type': 'char', }, }, }, }) self.assertEqual( list(dom.completion('Relatio')), ['Relation: ', 'Relation.Name: ']) def test_completion_boolean(self): "Test completion boolean" dom = DomainParser({ 'name': { 'string': "Active", 'name': 'active', 'type': 'boolean', }, }) self.assertEqual(list(dom.completion("Act")), ["Active: "]) self.assertEqual(list(dom.completion("Active:")), ["Active: ", "Active: True", "Active: False"]) self.assertEqual( list(dom.completion("Active: t")), ["Active: True", "Active: False"]) self.assertEqual( list(dom.completion("Active: f")), ["Active: False", "Active: True"]) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685599.0 tryton-7.0.24/tryton/tests/test_common_selection.py0000644000175000017500000000121114517761237020732 0ustar00cedced# This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. from unittest import TestCase from tryton.common.selection import freeze_value class SelectionTestCase(TestCase): "Test common selection" def test_freeze_value(self): "Test freeze_value" self.assertEqual(freeze_value({'foo': 'bar'}), (('foo', 'bar'),)) self.assertEqual(freeze_value([1, 42, 2, 3]), (1, 42, 2, 3)) self.assertEqual(freeze_value('foo'), 'foo') self.assertEqual( freeze_value({'foo': {'bar': 42}}), (('foo', (('bar', 42),)),)) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685599.0 tryton-7.0.24/tryton/tests/test_common_timedelta.py0000644000175000017500000000672214517761237020731 0ustar00cedced# This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. import datetime as dt from unittest import TestCase from tryton.common.timedelta import DEFAULT_CONVERTER, format, parse class TimeDeltaTestCase(TestCase): "Test common timedelta" def _format_values(self): return [ (None, ''), (dt.timedelta(), '00:00'), (dt.timedelta(days=3, hours=5, minutes=30), '3d 05:30'), (dt.timedelta(weeks=48), '11M 6d'), (dt.timedelta(weeks=50), '11M 2w 6d'), (dt.timedelta(weeks=52), '12M 4d'), (dt.timedelta(days=360), '12M'), (dt.timedelta(days=364), '12M 4d'), (dt.timedelta(days=365), '1Y'), (dt.timedelta(days=366), '1Y 1d'), (dt.timedelta(hours=2, minutes=5, seconds=10), '02:05:10'), (dt.timedelta(minutes=15, microseconds=42), '00:15:00.000042'), (dt.timedelta(days=1, microseconds=42), '1d .000042'), (dt.timedelta(seconds=-1), '-00:00:01'), (dt.timedelta(days=-1, hours=-5, minutes=-30), '-1d 05:30'), ] def _time_only_converter(self): converter = {} converter['s'] = DEFAULT_CONVERTER['s'] converter['m'] = DEFAULT_CONVERTER['m'] converter['h'] = DEFAULT_CONVERTER['h'] converter['d'] = 0 return converter def _time_only_converter_values(self): return [ (None, ''), (dt.timedelta(), '00:00'), (dt.timedelta(days=5, hours=5, minutes=30), '125:30'), (dt.timedelta(hours=2, minutes=5, seconds=10), '02:05:10'), (dt.timedelta(minutes=15, microseconds=42), '00:15:00.000042'), (dt.timedelta(days=1, microseconds=42), '24:00:00.000042'), (dt.timedelta(seconds=-1), '-00:00:01'), (dt.timedelta(days=-1, hours=-5, minutes=-30), '-29:30'), ] def test_format(self): "Test format" for timedelta, text in self._format_values(): self.assertEqual(format(timedelta), text, msg="format(%r)" % timedelta) def test_format_time_only_converter(self): "Test format with time only converter" converter = self._time_only_converter() for timedelta, text in self._time_only_converter_values(): self.assertEqual(format(timedelta, converter), text, msg="format(%r)" % timedelta) def _parse_values(self): return self._format_values() + [ (dt.timedelta(), ' '), (dt.timedelta(), 'foo'), (dt.timedelta(days=1.5), '1.5d'), (dt.timedelta(days=-2), '1d -1d'), (dt.timedelta(hours=1, minutes=5, seconds=10), '1:5:10:42'), (dt.timedelta(hours=2), '1: 1:'), (dt.timedelta(hours=.25), ':15'), (dt.timedelta(hours=1), '1h'), (dt.timedelta(hours=.25), '.25h'), ] def test_parse(self): "Test parse" for timedelta, text in self._parse_values(): self.assertEqual(parse(text), timedelta, msg="parse(%r)" % text) def test_parse_time_only_converter(self): "Test parse with time only converter" converter = self._time_only_converter() for timedelta, text in self._time_only_converter_values(): self.assertEqual(parse(text, converter), timedelta, msg="parse(%r)" % text) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1700068210.0 tryton-7.0.24/tryton/translate.py0000644000175000017500000001500314525175562015174 0ustar00cedced# This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. "Translate" import gettext import locale import logging import os import sys from gi.repository import Gtk from tryton.config import CURRENT_DIR _ = gettext.gettext _LOCALE2WIN32 = { 'af_ZA': 'Afrikaans_South Africa', 'ar_AE': 'Arabic_UAE', 'ar_BH': 'Arabic_Bahrain', 'ar_DZ': 'Arabic_Algeria', 'ar_EG': 'Arabic_Egypt', 'ar_IQ': 'Arabic_Iraq', 'ar_JO': 'Arabic_Jordan', 'ar_KW': 'Arabic_Kuwait', 'ar_LB': 'Arabic_Lebanon', 'ar_LY': 'Arabic_Libya', 'ar_MA': 'Arabic_Morocco', 'ar_OM': 'Arabic_Oman', 'ar_QA': 'Arabic_Qatar', 'ar_SA': 'Arabic_Saudi_Arabia', 'ar_SY': 'Arabic_Syria', 'ar_TN': 'Arabic_Tunisia', 'ar_YE': 'Arabic_Yemen', 'az-Cyrl-AZ': 'Azeri_Cyrillic', 'az-Latn-AZ': 'Azeri_Latin', 'be_BY': 'Belarusian_Belarus', 'bg_BG': 'Bulgarian_Bulgaria', 'bs_BA': 'Serbian (Latin)', 'ca_ES': 'Catalan_Spain', 'cs_CZ': 'Czech_Czech Republic', 'da_DK': 'Danish_Denmark', 'de_AT': 'German_Austrian', 'de_CH': 'German_Swiss', 'de_DE': 'German_Germany', 'de_LI': 'German_Liechtenstein', 'de_LU': 'German_Luxembourg', 'el_GR': 'Greek_Greece', 'en_AU': 'English_Australian', 'en_BZ': 'English_Belize', 'en_CA': 'English_Canadian', 'en_IE': 'English_Irish', 'en_JM': 'English_Jamaica', 'en_TT': 'English_Trinidad', 'en_US': 'English_USA', 'en_ZW': 'English_Zimbabwe', 'es_AR': 'Spanish_Argentina', 'es_BO': 'Spanish_Bolivia', 'es_CL': 'Spanish_Chile', 'es_CO': 'Spanish_Colombia', 'es_CR': 'Spanish_Costa_Rica', 'es_DO': 'Spanish_Dominican_Republic', 'es_EC': 'Spanish_Ecuador', 'es_ES': 'Spanish_Spain', 'es_ES_tradnl': 'Spanish_Traditional_Sort', 'es_GT': 'Spanish_Guatemala', 'es_HN': 'Spanish_Honduras', 'es_MX': 'Spanish_Mexican', 'es_NI': 'Spanish_Nicaragua', 'es_PA': 'Spanish_Panama', 'es_PE': 'Spanish_Peru', 'es_PR': 'Spanish_Puerto_Rico', 'es_PY': 'Spanish_Paraguay', 'es_SV': 'Spanish_El_Salvador', 'es_UY': 'Spanish_Uruguay', 'es_VE': 'Spanish_Venezuela', 'et_EE': 'Estonian_Estonia', 'eu_ES': 'Basque_Spain', 'fa_IR': 'Farsi_Iran', 'fi_FI': 'Finnish_Finland', 'fr_BE': 'French_Belgian', 'fr_CA': 'French_Canadian', 'fr_CH': 'French_Swiss', 'fr_FR': 'French_France', 'fr_LU': 'French_Luxembourg', 'fr_MC': 'French_Monaco', 'ga': 'Scottish Gaelic', 'gl_ES': 'Galician_Spain', 'gu': 'Gujarati_India', 'he_IL': 'Hebrew_Israel', 'hi_IN': 'Hindi', 'hr_HR': 'Croatian', 'hu_HU': 'Hungarian', 'hu': 'Hungarian_Hungary', 'hy_AM': 'Armenian', 'id_ID': 'Indonesian_indonesia', 'is_IS': 'Icelandic_Iceland', 'it_CH': 'Italian_Swiss', 'it_IT': 'Italian_Italy', 'ja_JP': 'Japanese_Japan', 'ka_GE': 'Georgian_Georgia', 'kk_KZ': 'Kazakh', 'km_KH': 'Khmer', 'kn_IN': 'Kannada', 'ko_IN': 'Konkani', 'ko_KR': 'Korean_Korea', 'lo_LA': 'Lao_Laos', 'lt_LT': 'Lithuanian_Lithuania', 'lv_LV': 'Latvian_Latvia', 'mi_NZ': 'Maori', 'mi_NZ': 'Maori', 'mi_NZ': 'Maori', 'mk_MK': 'Macedonian', 'ml_IN': 'Malayalam_India', 'mn': 'Cyrillic_Mongolian', 'mr_IN': 'Marathi', 'ms_BN': 'Malay_Brunei_Darussalam', 'ms_MY': 'Malay_Malaysia', 'nb_NO': 'Norwegian_Bokmal', 'nl_BE': 'Dutch_Belgian', 'nl_NL': 'Dutch_Netherlands', 'nn_NO': 'Norwegian-Nynorsk_Norway', 'ph_PH': 'Filipino_Philippines', 'pl_PL': 'Polish_Poland', 'pt_BR': 'Portuguese_Brazil', 'pt_PT': 'Portuguese_Portugal', 'ro_RO': 'Romanian_Romania', 'ru_RU': 'Russian_Russia', 'sa_IN': 'Sanskrit', 'sk_SK': 'Slovak_Slovakia', 'sl_SI': 'Slovenian_Slovenia', 'sq_AL': 'Albanian_Albania', 'sr_CS': 'Serbian (Cyrillic)_Serbia and Montenegro', 'sv_FI': 'Swedish_Finland', 'sv_SE': 'Swedish_Sweden', 'sw_KE': 'Swahili', 'ta_IN': 'Tamil', 'th_TH': 'Thai_Thailand', 'tr_IN': 'Urdu', 'tr_TR': 'Turkish_Turkey', 'tt_RU': 'Tatar', 'uk_UA': 'Ukrainian_Ukraine', 'uz-Cyrl_UZ': 'Uzbek_Cyrillic', 'uz-Latn_UZ': 'Uzbek_Latin', 'vi_VN': 'Vietnamese_Viet Nam', 'zh_CN': 'Chinese_PRC', 'zh_HK': 'Chinese_Hong_Kong', 'zh_MO': 'Chinese_Macau', 'zh_SG': 'Chinese_Singapore', 'zh_TW': 'Chinese_Taiwan', } def setlang(lang=None, locale_dict=None): "Set language" locale_dir = os.path.join(CURRENT_DIR, 'data/locale') if not os.path.isdir(locale_dir): try: import importlib.resources ref = importlib.resources.files('tryton') / 'data/locale' with importlib.resources.as_file(ref) as path: locale_dir = path except ImportError: import pkg_resources locale_dir = pkg_resources.resource_filename( 'tryton', 'data/locale') if lang: encoding = locale.getdefaultlocale()[1] if not encoding: encoding = 'UTF-8' if encoding.lower() in ('utf', 'utf8'): encoding = 'UTF-8' if encoding == 'cp1252': encoding = '1252' try: lang2 = locale.normalize(lang).split('.')[0] if os.name == 'nt': lang2 = _LOCALE2WIN32.get(lang2, lang2) elif sys.platform == 'darwin': encoding = 'UTF-8' # ensure environment variable are str lang, lang2, encoding = str(lang), str(lang2), str(encoding) os.environ['LANGUAGE'] = lang os.environ['LC_ALL'] = lang2 + '.' + encoding os.environ['LC_MESSAGES'] = lang2 + '.' + encoding os.environ['LANG'] = lang + '.' + encoding locale.setlocale(locale.LC_ALL, lang2 + '.' + encoding) except locale.Error: logging.getLogger(__name__).info( _('Unable to set locale %s') % lang2 + '.' + encoding) if os.path.isdir(locale_dir): gettext.bindtextdomain('tryton', locale_dir) gettext.textdomain('tryton') if locale_dict: conv = locale.localeconv() for field in list(locale_dict.keys()): if field == 'date': continue conv[field] = locale_dict[field] locale.localeconv = lambda: conv def set_language_direction(direction): if direction == 'rtl': direction = Gtk.TextDirection.RTL else: direction = Gtk.TextDirection.LTR Gtk.Widget.set_default_direction(direction) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685599.0 tryton-7.0.24/tryton.desktop0000644000175000017500000000230614517761237014223 0ustar00cedced[Desktop Entry] Version=1.0 Type=Application Name=Tryton GenericName=Client for the Tryton Application Platform GenericName[ca_ES]=Client per l'aplicació Tryton GenericName[de]=Client für die Tryton Applikationsplattform GenericName[es_AR]=Cliente para la aplicación Tryton GenericName[es_EC]=Cliente para la aplicación Tryton GenericName[es_ES]=Cliente para la aplicación Tryton GenericName[es_CO]=Cliente para la Aplicación Tryton GenericName[fr]=Client de la plate-forme applicative Tryton GenericName[ru]=Клиент для платформы приложений Tryton GenericName[sl]=Odjemalec za Tryton programsko platformo Comment=Access Tryton server Comment[de]=Verbindet zu einem Tryton Server Comment[es_AR]=Acceso al servidor Tryton Comment[es_EC]=Acceso al servidor Tryton Comment[ca_ES]=Accés al servidor Tryton Comment[es_ES]=Acceso al servidor Tryton Comment[es_CO]=Acceso al servidor Tryton Comment[fr]=Accéder au serveur Tryton Comment[ru]=Подключение к серверу Tryton Comment[sl]=Dostop do Tryton strežnika Keywords=Business;Management;Enterprise;ERP;Framework;Client; Exec=tryton %u Icon=tryton-icon Terminal=false MimeType=application/tryton; Categories=Office;Finance; ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1745680285.2341115 tryton-7.0.24/tryton.egg-info/0000755000175000017500000000000015003173635014307 5ustar00cedced././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1745680284.0 tryton-7.0.24/tryton.egg-info/PKG-INFO0000644000175000017500000000545115003173634015410 0ustar00cedcedMetadata-Version: 2.4 Name: tryton Version: 7.0.24 Summary: Tryton desktop client Home-page: http://www.tryton.org/ Download-URL: http://downloads.tryton.org/7.0/ Author: Tryton Author-email: foundation@tryton.org License: GPL-3 Project-URL: Bug Tracker, https://bugs.tryton.org/ Project-URL: Documentation, https://docs.tryton.org/latest/client-desktop/ Project-URL: Forum, https://www.tryton.org/forum Project-URL: Source Code, https://code.tryton.org/tryton Keywords: business application ERP Platform: any Classifier: Development Status :: 5 - Production/Stable Classifier: Environment :: X11 Applications :: GTK Classifier: Framework :: Tryton Classifier: Intended Audience :: End Users/Desktop Classifier: License :: OSI Approved :: GNU General Public License (GPL) Classifier: Natural Language :: Bulgarian Classifier: Natural Language :: Catalan Classifier: Natural Language :: Chinese (Simplified) Classifier: Natural Language :: Czech Classifier: Natural Language :: Dutch Classifier: Natural Language :: English Classifier: Natural Language :: Finnish Classifier: Natural Language :: French Classifier: Natural Language :: German Classifier: Natural Language :: Hungarian Classifier: Natural Language :: Indonesian Classifier: Natural Language :: Italian Classifier: Natural Language :: Persian Classifier: Natural Language :: Polish Classifier: Natural Language :: Portuguese (Brazilian) Classifier: Natural Language :: Romanian Classifier: Natural Language :: Russian Classifier: Natural Language :: Slovenian Classifier: Natural Language :: Spanish Classifier: Natural Language :: Turkish Classifier: Natural Language :: Japanese Classifier: Natural Language :: Ukrainian Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.8 Classifier: Programming Language :: Python :: 3.9 Classifier: Programming Language :: Python :: 3.10 Classifier: Programming Language :: Python :: 3.11 Classifier: Programming Language :: Python :: 3.12 Classifier: Topic :: Office/Business Requires-Python: >=3.8 License-File: LICENSE Requires-Dist: pycairo Requires-Dist: python-dateutil Requires-Dist: PyGObject<3.51,>=3.19 Provides-Extra: calendar Requires-Dist: GooCalendar>=0.7; extra == "calendar" Provides-Extra: sound Requires-Dist: playsound; extra == "sound" Dynamic: author Dynamic: author-email Dynamic: classifier Dynamic: description Dynamic: download-url Dynamic: home-page Dynamic: keywords Dynamic: license Dynamic: license-file Dynamic: platform Dynamic: project-url Dynamic: provides-extra Dynamic: requires-dist Dynamic: requires-python Dynamic: summary tryton ====== The desktop client of Tryton. Tryton is business software, ideal for companies of any size, easy to use, complete and 100% Open Source. It provides modularity, scalability and security. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1745680285.0 tryton-7.0.24/tryton.egg-info/SOURCES.txt0000644000175000017500000002505215003173635016177 0ustar00cedcedCHANGELOG COPYRIGHT LICENSE MANIFEST.in README.rst catalan.nsh english.nsh farsi.nsh french.nsh german.nsh make-darwin-installer.sh make-win32-installer.sh portuguese.nsh setup-freeze.py setup.cfg setup.nsi setup.py slovenian.nsh spanish.nsh tox.ini tryton.desktop bin/tryton doc/conf.py doc/glossary.rst doc/index.rst doc/installation.rst doc/releases.rst doc/requirements-doc.txt doc/usage.rst tryton/__init__.py tryton/bus.py tryton/client.py tryton/config.py tryton/device_cookie.py tryton/exceptions.py tryton/fingerprints.py tryton/jsonrpc.py tryton/pyson.py tryton/rpc.py tryton/translate.py tryton.egg-info/PKG-INFO tryton.egg-info/SOURCES.txt tryton.egg-info/dependency_links.txt tryton.egg-info/not-zip-safe tryton.egg-info/requires.txt tryton.egg-info/top_level.txt tryton/action/__init__.py tryton/action/main.py tryton/common/__init__.py tryton/common/button.py tryton/common/cellrendererbinary.py tryton/common/cellrendererbutton.py tryton/common/cellrendererclickablepixbuf.py tryton/common/cellrenderercombo.py tryton/common/cellrendererfloat.py tryton/common/cellrendererinteger.py tryton/common/cellrenderertext.py tryton/common/cellrenderertoggle.py tryton/common/common.py tryton/common/completion.py tryton/common/datetime_.py tryton/common/domain_inversion.py tryton/common/domain_parser.py tryton/common/entry_position.py tryton/common/environment.py tryton/common/focus.py tryton/common/htmltextbuffer.py tryton/common/number_entry.py tryton/common/popup_menu.py tryton/common/richtext.py tryton/common/selection.py tryton/common/timedelta.py tryton/common/underline.py tryton/common/widget_style.py tryton/data/locale/tryton.pot tryton/data/locale/bg/LC_MESSAGES/tryton.mo tryton/data/locale/bg/LC_MESSAGES/tryton.po tryton/data/locale/ca/LC_MESSAGES/tryton.mo tryton/data/locale/ca/LC_MESSAGES/tryton.po tryton/data/locale/cs/LC_MESSAGES/tryton.mo tryton/data/locale/cs/LC_MESSAGES/tryton.po tryton/data/locale/de/LC_MESSAGES/tryton.mo tryton/data/locale/de/LC_MESSAGES/tryton.po tryton/data/locale/es/LC_MESSAGES/tryton.mo tryton/data/locale/es/LC_MESSAGES/tryton.po tryton/data/locale/es_419/LC_MESSAGES/tryton.mo tryton/data/locale/es_419/LC_MESSAGES/tryton.po tryton/data/locale/et/LC_MESSAGES/tryton.mo tryton/data/locale/et/LC_MESSAGES/tryton.po tryton/data/locale/fa/LC_MESSAGES/tryton.mo tryton/data/locale/fa/LC_MESSAGES/tryton.po tryton/data/locale/fi/LC_MESSAGES/tryton.mo tryton/data/locale/fi/LC_MESSAGES/tryton.po tryton/data/locale/fr/LC_MESSAGES/tryton.mo tryton/data/locale/fr/LC_MESSAGES/tryton.po tryton/data/locale/hu/LC_MESSAGES/tryton.mo tryton/data/locale/hu/LC_MESSAGES/tryton.po tryton/data/locale/id/LC_MESSAGES/tryton.mo tryton/data/locale/id/LC_MESSAGES/tryton.po tryton/data/locale/it/LC_MESSAGES/tryton.mo tryton/data/locale/it/LC_MESSAGES/tryton.po tryton/data/locale/ja_JP/LC_MESSAGES/tryton.mo tryton/data/locale/ja_JP/LC_MESSAGES/tryton.po tryton/data/locale/lo/LC_MESSAGES/tryton.mo tryton/data/locale/lo/LC_MESSAGES/tryton.po tryton/data/locale/lt/LC_MESSAGES/tryton.mo tryton/data/locale/lt/LC_MESSAGES/tryton.po tryton/data/locale/nl/LC_MESSAGES/tryton.mo tryton/data/locale/nl/LC_MESSAGES/tryton.po tryton/data/locale/pl/LC_MESSAGES/tryton.mo tryton/data/locale/pl/LC_MESSAGES/tryton.po tryton/data/locale/pt/LC_MESSAGES/tryton.mo tryton/data/locale/pt/LC_MESSAGES/tryton.po tryton/data/locale/ro/LC_MESSAGES/tryton.mo tryton/data/locale/ro/LC_MESSAGES/tryton.po tryton/data/locale/ru/LC_MESSAGES/tryton.mo tryton/data/locale/ru/LC_MESSAGES/tryton.po tryton/data/locale/sl/LC_MESSAGES/tryton.mo tryton/data/locale/sl/LC_MESSAGES/tryton.po tryton/data/locale/tr/LC_MESSAGES/tryton.mo tryton/data/locale/tr/LC_MESSAGES/tryton.po tryton/data/locale/uk/LC_MESSAGES/tryton.mo tryton/data/locale/uk/LC_MESSAGES/tryton.po tryton/data/locale/zh_CN/LC_MESSAGES/tryton.mo tryton/data/locale/zh_CN/LC_MESSAGES/tryton.po tryton/data/pixmaps/tryton/LICENSE tryton/data/pixmaps/tryton/tryton-add.svg tryton/data/pixmaps/tryton/tryton-archive.svg tryton/data/pixmaps/tryton/tryton-arrow-down.svg tryton/data/pixmaps/tryton/tryton-arrow-left.svg tryton/data/pixmaps/tryton/tryton-arrow-right.svg tryton/data/pixmaps/tryton/tryton-arrow-up.svg tryton/data/pixmaps/tryton/tryton-attach.svg tryton/data/pixmaps/tryton/tryton-back.svg tryton/data/pixmaps/tryton/tryton-barcode-scanner.svg tryton/data/pixmaps/tryton/tryton-bookmark-border.svg tryton/data/pixmaps/tryton/tryton-bookmark.svg tryton/data/pixmaps/tryton/tryton-bookmarks.svg tryton/data/pixmaps/tryton/tryton-cancel.svg tryton/data/pixmaps/tryton/tryton-clear.svg tryton/data/pixmaps/tryton/tryton-close.svg tryton/data/pixmaps/tryton/tryton-copy.svg tryton/data/pixmaps/tryton/tryton-create.svg tryton/data/pixmaps/tryton/tryton-date.svg tryton/data/pixmaps/tryton/tryton-delete.svg tryton/data/pixmaps/tryton/tryton-download.svg tryton/data/pixmaps/tryton/tryton-drag.svg tryton/data/pixmaps/tryton/tryton-email.svg tryton/data/pixmaps/tryton/tryton-error.svg tryton/data/pixmaps/tryton/tryton-exit.svg tryton/data/pixmaps/tryton/tryton-export.svg tryton/data/pixmaps/tryton/tryton-filter.svg tryton/data/pixmaps/tryton/tryton-format-align-center.svg tryton/data/pixmaps/tryton/tryton-format-align-justify.svg tryton/data/pixmaps/tryton/tryton-format-align-left.svg tryton/data/pixmaps/tryton/tryton-format-align-right.svg tryton/data/pixmaps/tryton/tryton-format-bold.svg tryton/data/pixmaps/tryton/tryton-format-color-text.svg tryton/data/pixmaps/tryton/tryton-format-italic.svg tryton/data/pixmaps/tryton/tryton-format-underline.svg tryton/data/pixmaps/tryton/tryton-forward.svg tryton/data/pixmaps/tryton/tryton-history.svg tryton/data/pixmaps/tryton/tryton-icon.png tryton/data/pixmaps/tryton/tryton-icon.svg tryton/data/pixmaps/tryton/tryton-import.svg tryton/data/pixmaps/tryton/tryton-info.svg tryton/data/pixmaps/tryton/tryton-launch.svg tryton/data/pixmaps/tryton/tryton-link.svg tryton/data/pixmaps/tryton/tryton-log.svg tryton/data/pixmaps/tryton/tryton-menu.svg tryton/data/pixmaps/tryton/tryton-note.svg tryton/data/pixmaps/tryton/tryton-ok.svg tryton/data/pixmaps/tryton/tryton-open.svg tryton/data/pixmaps/tryton/tryton-print.svg tryton/data/pixmaps/tryton/tryton-public.svg tryton/data/pixmaps/tryton/tryton-question.svg tryton/data/pixmaps/tryton/tryton-refresh.svg tryton/data/pixmaps/tryton/tryton-remove.svg tryton/data/pixmaps/tryton/tryton-save.svg tryton/data/pixmaps/tryton/tryton-search.svg tryton/data/pixmaps/tryton/tryton-send.svg tryton/data/pixmaps/tryton/tryton-sound-off.svg tryton/data/pixmaps/tryton/tryton-sound-on.svg tryton/data/pixmaps/tryton/tryton-star-border.svg tryton/data/pixmaps/tryton/tryton-star.svg tryton/data/pixmaps/tryton/tryton-switch.svg tryton/data/pixmaps/tryton/tryton-translate.svg tryton/data/pixmaps/tryton/tryton-unarchive.svg tryton/data/pixmaps/tryton/tryton-undo.svg tryton/data/pixmaps/tryton/tryton-warning.svg tryton/data/pixmaps/tryton/tryton.icns tryton/data/pixmaps/tryton/tryton.ico tryton/data/pixmaps/tryton/tryton.svg tryton/data/sounds/LICENSE tryton/data/sounds/danger.wav tryton/data/sounds/success.wav tryton/gui/__init__.py tryton/gui/main.py tryton/gui/window/__init__.py tryton/gui/window/about.py tryton/gui/window/attachment.py tryton/gui/window/board.py tryton/gui/window/code_scanner.py tryton/gui/window/dblogin.py tryton/gui/window/email_.py tryton/gui/window/form.py tryton/gui/window/infobar.py tryton/gui/window/limit.py tryton/gui/window/log.py tryton/gui/window/nomodal.py tryton/gui/window/note.py tryton/gui/window/preference.py tryton/gui/window/revision.py tryton/gui/window/tabcontent.py tryton/gui/window/win_csv.py tryton/gui/window/win_export.py tryton/gui/window/win_form.py tryton/gui/window/win_import.py tryton/gui/window/win_search.py tryton/gui/window/window.py tryton/gui/window/wizard.py tryton/gui/window/view_board/__init__.py tryton/gui/window/view_board/action.py tryton/gui/window/view_board/view_board.py tryton/gui/window/view_form/__init__.py tryton/gui/window/view_form/model/__init__.py tryton/gui/window/view_form/model/field.py tryton/gui/window/view_form/model/group.py tryton/gui/window/view_form/model/record.py tryton/gui/window/view_form/screen/__init__.py tryton/gui/window/view_form/screen/screen.py tryton/gui/window/view_form/view/__init__.py tryton/gui/window/view_form/view/calendar_.py tryton/gui/window/view_form/view/form.py tryton/gui/window/view_form/view/graph.py tryton/gui/window/view_form/view/list.py tryton/gui/window/view_form/view/list_form.py tryton/gui/window/view_form/view/screen_container.py tryton/gui/window/view_form/view/calendar_gtk/__init__.py tryton/gui/window/view_form/view/calendar_gtk/calendar_.py tryton/gui/window/view_form/view/calendar_gtk/dates_period.py tryton/gui/window/view_form/view/calendar_gtk/toolbar.py tryton/gui/window/view_form/view/form_gtk/__init__.py tryton/gui/window/view_form/view/form_gtk/binary.py tryton/gui/window/view_form/view/form_gtk/calendar_.py tryton/gui/window/view_form/view/form_gtk/char.py tryton/gui/window/view_form/view/form_gtk/checkbox.py tryton/gui/window/view_form/view/form_gtk/dictionary.py tryton/gui/window/view_form/view/form_gtk/document.py tryton/gui/window/view_form/view/form_gtk/float.py tryton/gui/window/view_form/view/form_gtk/image.py tryton/gui/window/view_form/view/form_gtk/integer.py tryton/gui/window/view_form/view/form_gtk/many2many.py tryton/gui/window/view_form/view/form_gtk/many2one.py tryton/gui/window/view_form/view/form_gtk/multiselection.py tryton/gui/window/view_form/view/form_gtk/one2many.py tryton/gui/window/view_form/view/form_gtk/one2one.py tryton/gui/window/view_form/view/form_gtk/progressbar.py tryton/gui/window/view_form/view/form_gtk/pyson.py tryton/gui/window/view_form/view/form_gtk/reference.py tryton/gui/window/view_form/view/form_gtk/richtextbox.py tryton/gui/window/view_form/view/form_gtk/selection.py tryton/gui/window/view_form/view/form_gtk/state_widget.py tryton/gui/window/view_form/view/form_gtk/textbox.py tryton/gui/window/view_form/view/form_gtk/timedelta.py tryton/gui/window/view_form/view/form_gtk/url.py tryton/gui/window/view_form/view/form_gtk/widget.py tryton/gui/window/view_form/view/graph_gtk/__init__.py tryton/gui/window/view_form/view/graph_gtk/bar.py tryton/gui/window/view_form/view/graph_gtk/graph.py tryton/gui/window/view_form/view/graph_gtk/line.py tryton/gui/window/view_form/view/graph_gtk/pie.py tryton/gui/window/view_form/view/list_gtk/__init__.py tryton/gui/window/view_form/view/list_gtk/editabletree.py tryton/gui/window/view_form/view/list_gtk/widget.py tryton/plugins/__init__.py tryton/plugins/translation/__init__.py tryton/tests/__init__.py tryton/tests/test_common.py tryton/tests/test_common_domain_parser.py tryton/tests/test_common_selection.py tryton/tests/test_common_timedelta.py././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1745680284.0 tryton-7.0.24/tryton.egg-info/dependency_links.txt0000644000175000017500000000000115003173634020354 0ustar00cedced ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1698685648.0 tryton-7.0.24/tryton.egg-info/not-zip-safe0000644000175000017500000000000114517761320016540 0ustar00cedced ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1745680284.0 tryton-7.0.24/tryton.egg-info/requires.txt0000644000175000017500000000013615003173634016706 0ustar00cedcedpycairo python-dateutil PyGObject<3.51,>=3.19 [calendar] GooCalendar>=0.7 [sound] playsound ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1745680284.0 tryton-7.0.24/tryton.egg-info/top_level.txt0000644000175000017500000000000715003173634017035 0ustar00cedcedtryton