Canonical Voices

Gavin Panella

South, South 2, and Django Migrations

blockquote { background-color: #444; padding: 0.2em 1em; }

A couple of months ago we on the MAAS team found ourselves in a bit of a pickle: we needed to be able to support a product targeted at both Django <1.7 and Django ≥1.7 with database migrations. This is a problem because South is replaced by Django's own migration support in 1.7, and there are differences.

I emailed Andrew Godwin to ask his advice. He's the author of South 2and so apparently knows his stuff, but we also wondered if South 2 might be a way out of our mess. His reply confirmed him as knowledgable, kind, and helpful. Although he did not bless South 2 as our silver bullet, he did have some other useful advice instead.

I promised I would document our correspondence where others might learn from it, and this is it, somewhat overdue. I've edited it slightly for clarity.

Thanks Andrew!


Hi Andrew,

I found your south2 repository on GitHub today. It looks like you've not touched it in a while, but I wondered if I could ask you a few questions about it anyway? There's a lot of context but it boils down to two-ish questions:

  1. What would you recommend for transitioning a packaged product (i.e. one which we don't provide as a service) from South-based migrations to Django ≥1.7 migrations?

    As a general answer, I suggest the method described in the Django docs, which is to move the South migrations to a south_migrationsdirectory and generate new initial Django ones. As long as your users have South 1.0 or higher, that'll keep both versions running during a transition, and Django's automatic application of initial migrations makes things a lot easier. I don't recommend that you try and support both migration sets at the same time; make 1.7 or higher a hard dependency for a release. This obviously is a bit different for the case below, which I answered down there.

  2. How much work would be required to get south2 working?

    It was abandoned with good reason - it's around another two months of work to get it working remotely reliably, and I'm not sure it could be done at all without much more of a rewrite rather than the current source translation approach. I didn't abandon the idea lightly, but alas it just wasn't proving very stable.

We're in a tricky situation:

  • We have an application, MAAS, that we ship as a package in Ubuntu, i.e. end-users install it. It uses PostgreSQL.
  • It's supported in Ubuntu 14.04 (Trusty) and will be supported until April 2019. Trusty ships with Django 1.6, and this won't change (only security fixes and fixes for very serious bugs are back-ported).
  • Django 1.7 is now available in the development version of Ubuntu (Vivid).
  • Django 1.7 or later will be in the next LTS (Long Term Support) version of Ubuntu, out next year. (Trusty is the most recent LTS release.)
  • We have been using South for several years.
  • To support MAAS in Trusty we may need to back-port migrations from trunk. Once we base trunk on Django ≥1.7 we can't back-port directly; we'd need to recreate any migrations with South.
  • However, we also need a seamless upgrade path for users on Trusty when they upgrade to the next LTS release, where they can skip right over three intermediate releases of Ubuntu.
  • Between Trusty and the next LTS (hereafter just "Next"), the upgrade path might look like (where mXXX = "migration XXX"):

    Trusty -- m134 -- m135 -- m136 -- m137 (then EOL)
    \ \ \ \
    Next -- m0 ---- m1 ---- m2 ---- m3 ---- m4 ---- ...

    In other words, Trusty and the next LTS share a common ancestor in South migration 134; the Django ≥1.7 migration baseline is derived at that point.

    At any point after that a user could choose to upgrade to the next LTS. If they upgrade from an installation that's got m136, we could map that over to m2 in the new migrations model, tell Django to fake-apply m0, m1, and m2, then proceed from there.

  • In truth, a user could choose to upgrade from Trusty to Next beforehaving applied m134 because users can choose to follow only security fixes, and not updates. (They can choose to follow nothing at all, but that's getting into a very grey area w.r.t. support.)

    In this situation we'd want to apply all remaining South migrations up to at least m134 before switching over to the new Django migrations model.

    On the other hand, there may be a way to prevent a Trusty → Next upgrade based on a precondition, e.g. "m134 or greater is needed", but I don't currently know how that would be implemented.

  • There's a risk of South migrations not matching up to Django ≥1.7 migrations. That would most likely be an issue with our process, but it could be a software issue too.
  • With a variety of automated testing we can mitigate a lot of the process risk, and catch software issues early.
  • However, that all adds up to quite a lot of work.
  • Another option entirely would be for us to invest time into south2 and switch everything over to Django ≥1.7 migrations. That sounds like it would be a lot simpler, and thus carry a lot less risk.
  • The thing I don't know, which I hope you can answer, is how much work might it be to get south2 to a point where this would be possible? What would the ongoing maintenance look like?
  • What would you recommend?

    There's no clean solution, sorry. I'd document having to apply the most recent migrations before switching (and perhaps have a code entry on startup in the 1.7 dependent version that checks the south_migrations table directly and hard fails if you didn't), then have people clean switch over to the latest release.

    Can I ask why you won't just ship a newer version of Django with the newer releases of MAAS, even on Trusty? I know OS packaging is a tough thing to get around, but trying to backport migrations to work on South and older releases is only going to bring you pain (South is much more limited than Django migrations, and you might have to do a lot of manual workarounds).

    South2 isn't going to work - don't go down that path, I abandoned it for good reason, I'm not even sure the automated source translation approach is possible and a rewrite would take months. You're better off somehow shipping 1.7 bundled or as some kind of special dependency.

Read more
Sergio Schvezov

Snappy rolling back on kernel panic

Image you get an update and the kernel panics with that update, what are you to do? Suppose now that you have a snappy based system, this is automatically solved for you.

Here’s a short video showing this on a Beagle Bone Black, the concept is quite simple, if there is a panic, we revert to the last known state. In this video I inject an initrd that panics on boot after issuing a snappy update and before rebooting into the update.

In this video you observe the following:

  • Manually checking for updates.
  • Manually applying the updates.
  • How the a/b boot selection is done.
  • Implicitly observe the internal (subject to change) layout of snappy-boot and system-a or system-b selection.
  • Rebooting into the new kernel.
  • Observing a panic and rebooting back into the working system.

In the normal case this would seldom happen (the broken boot aside) as the autopiloting feature is enabled by default today, which you can check by running snappy config ubuntu-core

Read more
Daniel Holbach

snappyIt’d be a bit of a stretch to call UOS Snappy Online Summit, but Snappy definitely was talk of the town this time around. It was also picked up by tech news sites, who not always depicted Ubuntu’s plans accurately. :-)

Anyway… if you missed some of the sessions, you can always go back, watch the videos of the sessions and check the notes. Here’s the links to the sessions which already happened:

Which leaves us with today, 7th May 2015! You can still join these sessions today – we’ll be glad to hear your input and ideas! :-)

  • 14:00 UTC: Ubuntu Core Brainstorm – Calling all Snappy pioneers
    Snappy and Ubuntu Core are still hot off the press, but it’s already clear that they’re going to bring a lot of opportunities and will make the lives of developers a lot easier. Let’s get together, brainstorm and find out where Snappy can be used in the future, which communities/tools/frameworks can be joined by it, which software should be ported to it and which crazy nice tutorials/demos can be easily put together. Anything goes, join us, no matter if on IRC or in the hangout!
  • 16:00 UTC: Snappy Q&A
    Everything you always wanted to know about Snappy and Ubuntu Core. Bring your questions here! Bring your friends as well. We’ll make sure to have all the relevant experts here.
  • 18:00 UTC: Replace ifupdown with networkd on snappy / cloud / server for 16.04
    What the title says. Networkers, we’ll need you here. :-)

The above are just my suggestions, obviously there’s loads of other good stuff on the schedule today! See you later!

Read more
Michael Hall

Ubuntu has been talking a lot about convergence lately, it’s something that we believe is going to be revolutionary and we want to be at the forefront of it. We love the idea of it, but so far we haven’t really had much experience with the reality of it.

image20150423_164034801I got my first taste of that reality two weeks ago, while at a work sprint in London. While Canonical has an office in London, it had other teams sprinting there, so the Desktop sprint I was at was instead held at a hotel. We planned to visit the office one day that week, it would be my first visit to any Canonical office, as well as my first time working at an actual office in several years. However, we also planned to meet up with the UK loco for release drinks that evening. This meant that we had to decide between leaving our laptops at the hotel, thus not having them to work on at the office, or taking them with us, but having to carry them around the pub all evening.

I chose to leave my laptop behind, but I did take my phone (Nexus 4 running Ubuntu) with me. After getting a quick tour of the office, I found a vacant seat at a desk, and pulled out my phone. Most of my day job can be done with the apps on my phone: I have email, I have a browser, I have a terminal with ssh, I can respond to our community everywhere they are active.

I spent the next couple of hours doing work, actual work, on my phone. The only problem I had was that I was doing it on a small screen, and I was burning through my battery. At one point I looked up and realized that the vacant desk I was sitting at was equipped with a laptop docking station. It had also a USB hub and an HDMI monitor cable available. If I had a slimport cable for my phone, I might have been able to plug it into this docking station and both power my phone and get a bigger screen to work with.

If I could have done that, I would have achieved the full reality of convergence, and it would have been just like if I had brought my laptop with me. Only with this I was able to simply slide it into my pocket when it was time to leave for drinks. It was tantalizingly close, I got a little taste of what it’s going to be like, and now I’m craving more of it.

Read more
Colin Ian King

An on-going background project of mine is to add various interesting system stress tests to stress-ng.  Over the past several months I've been looking at the ways to exercise various less used or obscure system calls just to add more kernel coverage to the tool.

  • rlimit - generate tens of thousands of SIGXFSZ and many SIGXCPU signals
  • itimer - exercise ITIMER_PROF and generate SIGPROF signals
  • mlock - lock and unlock pages with mlock()/munlock()
  • timerfd - exercise rapid CLOCK_REALTIME events by select() and read() on a timerfd.
  • memfd - exercise anonymous populated page memory mapping and unmappoing using memfd.
  • more aggressive affinity stressor changes to force more CPU IPIs
  • hdd - add readv/writev I/O option
  • tee - tee data between a writer and reader process using tee()
  • crypt - encrypt data with MD5, SHA-256 and SHA-512 using libcrypt
  • mmapmany - perform tens of thousands of memory maps/unmaps to exhaust the per-process mapping limit.
  • zombie - fill up process table with tens of thousands of zombie processes
  • str - heavily exercise a range of glibc string functions
  • xattr - exercise file extended attributes
  • readahead - random reads with readaheads
  • vm - add a rowhammer memory stressor
..as well as extra per-stressor configuration settings and a lot of code clean up and bug fixing.

I've recently been using stress-ng to exercise various kernels on a range of hardware and it has been useful in forcing bugs, especially with the memory specific stressors that seem to trip low memory corner cases.

stress-ng 0.04.01 will be soon available in Ubuntu 15.10 Wily Werewolf.  Visit the stress-ng project page for more details.

Read more
Colin Ian King

powerstat improvements with RAPL

The Linux Running Average Power Limit (RAPL) interface was introduced about 2 years ago in the Linux kernel and allows userspace to read the power consumption from various x86 System-on-a-Chip (SoC) power domains.  The power domains range from the SoC package, CPU core, DRAM controller and graphics power plane.

It appears that the Intel energy status MSRs can be read very rapidly and the resolution is exceptionally good; however, reading the MSR too frequently will consume some power when using the RAPL interface.

I've improved powerstat to now use the RAPL interface with a new -R option (to measure just the total package power consumption).  A new -D option will show all the RAPL domain measurements available.  RAPL measurements are very responsive and one can easily correlate power spikes with bursts of system activity.

Finally, I have added a basic histogram output with the new -H option. This will plot histograms of the power measurements and CPU load from the stats gathered during the powerstat run.

Powerstat 0.01.37 is available in Ubuntu 15.10 Wily Werewolf and the source is available from the git repository.

Read more
pitti

Almost every new autopkgtest release brings some small improvements, but 3.14 got some reboot related changes worth pointing out.

First of all, I simplified and unified the implementation of rebooting across all runners that support it (ssh, lxc, and qemu). If you use a custom setup script for 3.14 got some reboot related changes worth pointing out.

First of all, I simplified and unified the implementation of rebooting across all runners that support it (ssh, lxc, and qemu). If you use a custom setup script for adt-virt-ssh you might have to update it: Previously, the setup script needed to respond to a reboot function to trigger a reboot, wait for the testbed to go down, and come back up. This got split into issuing the actual reboot system command directly by adt-run itself on the testbed, and the “wait for go down and back up” part. The latter now has a sensible default implementation: it simply waits for the ssh port to become unavailable, and then waits for ssh to respond again; most testbeds should be fine with that. You only need to provide the new wait-reboot function in your ssh setup script if you need to do anything else (such as re-enabling ssh after reboot). Please consult the manpage and the updated SKELETON for details.

The ssh runner gained a new --reboot option to indicate that the remote testbed can be rebooted. This will automatically declare the reboot testbed capability and thus you can now run rebooting tests without having to use a setup script. This is very useful for running tests on real iron.

Finally, in testbeds which support rebooting your tests will now find a new /tmp/autopkgtest-reboot-prepare command. Like /tmp/autopkgtest-reboot it takes an arbitrary “marker”, saves the current state, restores it after reboot and re-starts your test with the marker; however, it will not trigger the actual reboot but expects the test to do that. This is useful if you want to test a piece of software which does a reboot as part of its operation, such as a system-image upgrade. Another use case is testing kernel crashes, kexec or another “nonstandard” way of rebooting the testbed. README.package-tests shows an example how this looks like.

3.14 is now available in Debian unstable and Ubuntu wily. As usual, for older releases you can just grab the deb and install it, it works on all supported Debian and Ubuntu releases.

Enjoy, and let me know if you run into troubles or have questions!

Read more
David Callé

Are you involved in Ubuntu phone, desktop, cloud or IoT development? Voice your opinion on what factors contribute to your choice of developing on Ubuntu by getting involved in the biggest developer survey yet.

Vision Mobile have launched their 9th edition developer economics survey today, covering developer sentiment across platforms, revenues, apps, tools, APIs, segments and regions. This ambitious survey covers everything from mobile, desktop, cloud and IoT. Key insights from the survey will be provided as a free download in late July, and a free chapter from one of VisionMobile’s premium paid reports will also be given immediately upon completion, taking a close look at app profits & costs.

Tell us your thoughts about the latest developer trends, take the 10 minute survey now - some amazing prizes are up for grabs including the BQ Aquaris E4.5 Ubuntu Edition, Apple Sports Watch, iPhone 6, Oculus Rift Dev Kit + many more gadgets!

Read more
April Wang

Mark Shuttleworth昨晚揭晓了Ubuntu 15.10 的版本代号 Wily Werewolf。Wily在英文中一般用于描述易于适应环境锐敏足智的角色人物,而Werewolf则是传说中的一种神秘物种,小说中常常以人形出现但会在满月之日变成狼性的狼人 


Ubuntu 15.10 将会在今年10月份发布,而它的代码名也是至今为止Ubuntu版本名中第二个被重复的字母 (另外一组曾被重复使用的字母为H,相关版本名分别为Hardy Heron和Hoary Hedgehog)


为了昨晚有错过直播的筒子们另外再献上 , Mark现场的几段精彩keynote,致即将到来的Pocket PC!


“We [the Linux desktop] were always five, ten years behind. Always copying the other guy. Here, for the first time, we have this vision at the same time,” he said.

“It doesn’t matter [who had the idea first]. We’re living in a world where, if we make a great experience, all the applications that run on a Windows converged experience might also run on a Ubuntu converged experience.”

“I’d like us to rise above kind of saying ‘you’re copying our ideas’. We didn’t patent those ideas. We spoke very publicly about that vision because we wanted it to be true. I think it’s exciting for everybody that [it now is].”

配图源自:http://itsfoss.com/

顺便提一下,Ubuntu手机开发培训本周六落地南京,附近的小伙伴们别忘了立即报名啊。

Read more
Nicholas Skaggs

UOS 15.05 begins May 5th!

UOS is the Ubuntu Online Summit we hold each cycle to talk about what's happening in ubuntu. UOS 15.05 will be on May 5th - May 7th, 2015. It's time to make sure you are registered and get hyped.

To help with that, I thought would cover some sessions I'm most looking forward to.

SDK with Autopilot plugin
This session showcases a plugin written by a lovely member of the ubuntu community for the Ubuntu SDK. It will help make running and writing autopilot tests for your applications easier. Check out the demo!

Developing Unity 8
In the past I've talked about running Unity8, testing it, wanting to use it as my primary desktop, etc. This session goes even further to talk about how you can help actually develop unity8. Get a proper development setup going, learn where to pitch in and how to propose your work once complete!

Phone User Feedback
This peaks my interest as well. I'm keen to discuss ways to close the feedback loop from a user to developers. Improving that loop will make for a better phone and experience for everyone. Judging from the session description, it seems they will also tackle things like bug reporting, click package identification and other details important for getting feedback from a user.

Still not enough to peak your interest? Check out the full schedule here. There's a session or two for you in there.  I'll see you at UOS!

Read more
Daniel Holbach

Not sure if you saw Marks’ blog post earlier, but I’ll make sure to be watching the keynote at http://ubuntuonair.com/ at 14:00 UTC today. :-)

Read more
facundo


Me está costando ponerme a escribir algo en el blog. Más que nada porque no tengo mucho tiempo libre, y cuando lo tengo (y no lo aprovecho con la familia), me alejo un poco de la compu y veo series o pelis, o en la compu estoy sólo programando.

Tampoco tengo nada puntual que escribir, entonces me da fiaca hacer una recopilación de cosas, o detalles que sucedieron, etc.

Pero para ver si logramos vencer esta "inercia a arrancar", vamos a probar un nuevo esquema.  El otro día estaba escuchando una entrevista a Dolina, en el programa de Alivertii "Decime quien sos vos", y me enteré que a él le resulta más fácil, para escribir o producir textos, dictarlos. Entonces, la idea es dictarme yo mismo, grabando con el teléfono, y después voy a transcribir eso.

Obviamente lo voy a editar bastante, lo voy a toquetear, pero la idea es a ver si se logra que fluya más las cosas para contar y no terminar con dos o tres descripciones parcas y nada más.

Grabador de voz

En tren de contar algo de lo que fue sucediendo, el mes pasado hice el asado geek, que aunque es sólo un asado un domingo, realmente hay que ir a comprar un montón de cosas, y acomodar cosas en la casa, preparar todo, un largo etc. Después es un par de días de reorganizar todo de nuevo.

Se vino también el cumple de Malena, que implicó organizar la fiesta en el salón, la reunión en casa, etc. Y estamos armando una fiesta de cumpleaños con Moni, lo cual también lleva su tiempo de coordinación.

A nivel programación, estoy avanzando con varios proyectos, puntualmente CDPedia, fades, y Encuentro.

Con respecto a CDPedia, había preparado hace unos meses una imagen nueva para Huayra, que me pidió Karucha, pero revisando lo que se había armado nos dimos cuenta que había problemas en la búsqueda (vos ponías una palabra y jamás te traía un resultado).

Empecé a analizar el problema, y ví que no era algo que había salido mal en esa imagen, sino que teníamos un problema a nivel código, era algo a arreglar, así que me puse con eso y encontré que estábamos sacando mal títulos de las páginas (habían cambiado el formato interno).

Lo mejoré, y ahora no sólo saca bien los títulos sino que los saca en una etapa anterior, lo que es más eficiente. También metí un par de mejoras con respecto a la internacionalización.  En este momento estoy generando una imagen beta para ver si está todo bien, y ahí armar un set completo de imágenes, para distribuirlos. Luego, saldrá una beta en portugués, a ver cómo anda :)

El otro programa en el que estoy metiendo tiempo, como decía, es Encuentro. Cuando Humitos estuvo en casa (en esos días antes y después del asado geek) le metimos unas horas a este programa porque él está haciendo lo que es la integración con el backend de CDA, y ayudándolo y probando algunas cosas me di cuenta que la autenticación de Conectate cambió con lo cual no se podían bajar bien los videos.

Eso ya lo arreglé, pero todavía no lo liberé. Mi idea era esperar a Humitos para ver si metemos lo de CDA, pero creo que no lo va a tener para las próximas semanas, así que voy a hacer un release estos días, no sólo con ese fix sino también con otras mejoras y pequeñas correcciones que fui haciendo.

Finalmente, el otro fin de semana pasé un rato por FLISOL y luego me fui para la casa de Gilgamezh donde le estuvimos metiendo tiempo a fades, porque queremos hacer un release pronto con algunos features realmente copados ahí. Ya tenemos planeado con qué queremos llegar a la v3, nos falta poco :)

Read more
Michael Hall

A couple of years ago the Ubuntu download page introduced a way for users to make a financial contribution to the ongoing development of Ubuntu and it’s surrounding projects and community. Later a program was established within Canonical to make the money donated specifically for supporting the community available directly to members of the community who would use it to benefit the wider project.

During the last month, at the request of members of the Ubuntu community and the Community Council, we have undertaken a review of the this program. While conducting a more thorough analysis of the what was donated to us and when, it was discovered that we made an error in our initial reporting, which has unfortunately affected the accuracy of all subsequent reports as well.

What Happened?

Our first report, published in May of 2014, combined the amounts donated to the community slider and the amounts dispersed to the community during the previous four financial quarters. In that report we listed the amount donated from April 2013 to June 2013 as being a total of $34,353.63. However, when looking over all of the quarterly donations going back to the start of the program, we realized that this amount actually covered donations made from April 2013 all the way to October 2013.

This means that the figure contains both the amount donated during that Apr-Jun quarter, as well as duplicating the amounts listed as being donated for the Jul-Sep quarter, and a part of the Oct-Dec quarter. The actual amount donated during just the Apr-Jun 2013 quarter was $15,726.72. As a result of this, and the fact that it affected the carry over balanced for all subsequent reports, I have gone back and corrected all of these to reflect the correct figures.

Now for the questions:

Where are the updated reports?

The reports have not moved, you can still access them from the previously published URLs, and they are also listed on a new Reports page on the community website. The original report data has been preserved in a copy which is linked to at the top of each revised report.

Where did the money go?

No money has been lost or taken away from the program, this change is only a correction to the actual state of things. We had originally over-stated the amount that was donated, due to an error when reading the raw donation data at the time the first report was written.

How could a mistake like this happen?

The information we get is a summary of a summary of the raw data. At some point in the process the wrong number was put in the wrong place. All of these reports are manually written and verified, which often catches errors such as this, but in the very first report this error was missed.

Are these numbers trustworthy?

I understand that a reduction in the balance number, in conjunction with questions being raised about the operation of the program, will lead some people to question the honesty of this change. But the fact remains that we were asked to investigate this, we did find a discrepant, and correcting it publicly is the right thing for us to do, regardless of how it may look.

Is the community funding program in trouble?

Absolutely not. Even with this correction there has been more money donated to the community slider than we have been able to use. There’s still a lot more good that can be done, if you think you have a good use for some of it please fill out a request.

Read more
William Grant

Today we’re announcing early support for hosting Git repositories directly on Launchpad, as well as or instead of Bazaar branches. This has been by far the single most commonly requested feature from Launchpad code hosting for a long time; we’ve been working hard on it for several months now, and we’re very happy to be able to release it for general use.

This is distinct from the facility to import code from Git (and some other systems) into Bazaar that Launchpad has included for many years. Code imports are useful to aggregate information from all over the free software ecosystem in a unified way, which has always been one of the primary goals of Launchpad, and in the future we may add the facility to import code into Git as well. However, what we’re releasing today is native support: you can use git push to upload code to Launchpad, and your users and collaborators can use git clone to download it, in the same kind of way that you can with any Git server.

Our support is still in its early stages, and we still have several features to add to bring it up to parity with Bazaar hosting in Launchpad, as well as generally making it easier and more pleasant to use. We’ve released it before it’s completely polished because many people are clamouring to be able to use it and we’re ready to let you all do so. From here on in, we’ll be adding features, applying polish, and fixing bugs using Launchpad’s normal iterative deployment process: changes will be rolled out to production once they’re ready, so you’ll see the UI gradually improving over time.

What’s supported?

You can:

  • push Git repositories to Launchpad over SSH
  • clone repositories over git://, SSH, or HTTPS
  • see summary information on repositories and the branches they contain in the Launchpad web UI
  • follow links from the Launchpad web UI to a full-featured code browser (cgit)
  • push and clone private repositories, if you have a commercial subscription to Launchpad
  • propose merges from one branch to another, including in a different repository, provided that they are against the same project or package

What will be supported later?

Launchpad’s Bazaar support has grown many features over the years, and it will take some time to bring our Git support up to full parity with it and beyond. Git repositories use a somewhat different model from Bazaar branches, which we’ve had to account for in many places, and some facilities will require complete reimplementation before we can support them with Git.

Here’s an incomplete list of some of the features we hope to add:

  • useful subscriptions (currently only attribute change notifications work, which are not usually very interesting in themselves)
  • RSS feeds
  • mirroring
  • webhooks
  • an integrated code browser

See our help page for more known issues and instructions on using Launchpad with Git.

Helping out

This is a new service, and we welcome your feedback: you can ask questions in #launchpad on freenode IRC, on our launchpad-users mailing list, or on Launchpad Answers, and if you find a bug then please tell us about that too.

Launchpad is free software, licensed under the GNU AGPLv3. We’d be very happy to mentor people who want to help out with parts of this service, or to build things on top of it using our published API. Some preliminary documentation on this is on our developer wiki, and you can always contact us for help.

Read more
UbuntuTouch

[原]Windows7下安装Ubuntu双系统

Windows7和Ubuntu双系统的安装是非常容易的。在Ubuntu的安装的时候,已经提供了图形化的界面来帮助用户安装。你可以选择单单安装Ubuntu操作系统抹去原来的Windows操作系统,也可以选择与Windows操作系统共存,用Windows引导Ubuntu。但是这就造成了一个问题——一旦你想要删除Ubuntu操作系统,想要抹掉原先分给Ubuntu的磁盘空间,就会破坏Windows的MBR分区。因为在你按照原先系统安装盘的指令安装Ubuntu操作系统的时候,MBR分区的数据会被重写。

小编之前也有过这样的情况。后果就是在我删除整个Ubuntu分区后,windows也已经打不开了。在这个时候,其实你可以做一个工作来找回你“丢失”的Windows操作系统。修复MBR分区。比较常见的,比较“古朴”的方法就是进DOS使用fdisk /mbr命令。但是当时小编用的时候貌似还是不行。于是找到了以下的方法:进入windows系统,调出cmd,用bootrec /fixmbr命令,就能修复MBR分区了。

那么每次这样是不是很麻烦?能不能从源头上来解决呢?可以的。在安装Ubuntu操作系统的时候就可以解决这个问题。下面就跟着小编的这个步骤来安装操作系统,不破坏windows的正常使用。


1)下载Ubuntu desktop操作系统


可以在以下网址:http://www.ubuntu.com/download/desktop

下载到Ubuntu desktop的发行版。根据你自己的使用情况及CPU的情况来下载适合自己电脑的Ubuntu Desktop操作系统。




建议大家下载Ubuntu 14.10,因为在做Ubuntu手机、平板开发的时候是需要14.10的系统的。小编在这里选择64位的14.10操作系统



2)磁盘分区


在这一步中,你将要对你的磁盘进行分区。如果普通使用的话20G的磁盘空间就可以了,但是做开发的话是不够的。小编在这里给Ubuntu分了100G的空间。

在我的“计算机”上右键,选择“管理”,打开之后依次选择“存储——磁盘管理”,之后你就将看到你的硬盘的虚拟化的图像。其实还有其他的外接存储设备都会有显示。




在一个硬盘分区上右键选择“压缩卷”,跟着步骤走,输入你想要磁盘大小。


3)制作USB启动盘


找一个U盘,4G的吧,应该够了,插入电脑的USB接口。小编在这里使用的是UltraISO(http://cn.ultraiso.net/xiazai.html)这个软件。




“打开文件”,定位到你刚刚下载的Ubuntu的安装文件,“打开”




选择“启动”——“写入硬盘映像”




在这里一定要注意哦,这里“硬盘驱动器”这里是不是你的那个U盘。如果你插了其他的存储设备一定要看清,不然会造成不必要的损失。

然后点“写入”,静候直至出现“刻录成功”的字样。




至此,你的U盘安装盘就已经做好了。



4)安装Ubuntu操作系统


令人激动人心的时刻到了安装Ubuntu操作系统




开机的时候进入Boot Manager选择USB启动。各个电脑的进入Boot选项的方法都不同,但是无外乎F12,F10,F2等等的function键。




在这里你可以选择

  • 使用Ubuntu操作系统但并不安装

  • 安装Ubuntu

  • OEM安装

  • 检查磁盘

我们在这里选择“Install Ubuntu”(“安装Ubuntu”)


注:如果你看到以下画面,请:

  • 检查电脑USB口

  • 更换U盘重新制作USB启动盘

  • 插拔U盘查看是否是接触问题

最有可能的还是U盘的问题,所以请使用U盘芯片比较好的U盘,推荐用Sandisk





接着就是选择语言,wifi,等等的安装前的设置。

但是我们要注意一下,在安装类型的这里仔细看一下。








在这里需要注意下


安装类型选择其他选项




选择你刚刚在Windows下分出来的那个盘,选择“Ext4 日志文件系统” 挂载点“/”




这步做完,你的那个盘的格式会变为Ext4




然后在“安装启动引导器的设备”一栏里选择你的那个盘,小编这里是“sda6”(每个人的电脑可能显示的是不同的)




之后选择“格式化”——“现在安装”

在选择时区、设置用户名密码之后就会进入到安装过程




这一步之后就已经把Ubuntu安装到那个分区了。

电脑就会自动重启


5)设置引导


在如上步骤安装完之后,重启进入windows引导没有Ubuntu的。




在这一步就是要设置Ubuntu的引导。

进入windows之后,我们所使用的软件是EasyBCD(http://easybcd.softonic.cn/)





选择“Add New Entry”选择“Linux/BSD”——选择“GRUB2”并命名为Ubuntu,点击“Add Entry”





6)重新启动计算机,安装完成


重新启动计算机后,你就会看到Windows的引导里有你刚刚建立的Ubuntu的选项。选择Ubuntu就会进入Grub。然后就会进入Ubuntu操作系统。






这样的安装方式,就不怕以后删除Ubuntu之后不能启动windows了,但是小编还是希望大家不要删除Ubuntu的啦~


Ubuntu小编原创,欢迎转载!



作者:UbuntuTouch 发表于2015/4/8 8:53:44 原文链接
阅读:715 评论:0 查看评论

Read more
UbuntuTouch

[原]Ubuntu OS上的QML应用框架

在我们编写QML应用的时候,我们有时事先需要来考虑我们怎么使用一个好的框架来完成我们的应用。我们的应用有多少个页面,页面之间的导航到底是怎么样子的。这个对于我们一开始来设计我们的应用来说非常中要。在这篇文章中,我们来介绍如何在上层来设计我们的应用框架。


1)使用tab来创建一个平面的导航应用

我们可以使用我们的Ubuntu SDK来创建一个最基本的叫做TabApp的应用:

   

 

这样我们就生成了我们的一个最基本的应用。我们把应用的宽度和高度设为如下的值:

    width: units.gu(50)
    height: units.gu(75)

同时,我们也修改我们的Main.qml如下:

import QtQuick 2.0
import Ubuntu.Components 1.1
import Ubuntu.Components.ListItems 1.0 as ListItem

MainView {
    // objectName for functional testing purposes (autopilot-qt5)
    objectName: "mainView"

    // Note! applicationName needs to match the "name" field of the click manifest
    applicationName: "tabapp.ubuntu"

    /*
     This property enables the application to change orientation
     when the device is rotated. The default is false.
    */
    //automaticOrientation: true

    // Removes the old toolbar and enables new features of the new header.
    useDeprecatedToolbar: false

    width: units.gu(50)
    height: units.gu(75)


    Tabs {
        id: tabs

        Tab1 {
            objectName: "Tab1"
        }

        Tab2 {
            objectName: "Tab2"
        }
    }
}

在这里我们定义了两个Tab页面,分别为Tab1及Tab2。它们的内容分别为:

import QtQuick 2.0
import Ubuntu.Components 1.1

Tab {
    title: i18n.tr("Tab 1")

    Action {
        id: reloadAction
        text: "Reload"
        iconName: "reload"
        onTriggered: {
            console.log("reload is clicked")
        }
    }

    page: Page {
        Label {
            anchors.centerIn: parent
            text: i18n.tr("This is page one")
        }

        tools: ToolbarItems {
            ToolbarButton {
                action: reloadAction
            }
        }
    }
}

import QtQuick 2.0
import Ubuntu.Components 1.1

Tab {
    title: i18n.tr("Tab 2")

    page: Page {
        Label {
            anchors.centerIn: parent
            text: i18n.tr("This is page two")
        }
    }
}

这是一个最简单的Tab导航应用。我们在手机上运行:

    


所有的源码可以在地址下载:

git clone https://gitcafe.com/ubuntu/TabApp1.git


我们也可以把我们的Main.qml修改如下:

import QtQuick 2.0
import Ubuntu.Components 1.1
import Ubuntu.Components.ListItems 1.0 as ListItem

MainView {
    // objectName for functional testing purposes (autopilot-qt5)
    objectName: "mainView"

    // Note! applicationName needs to match the "name" field of the click manifest
    applicationName: "tabapp.ubuntu"

    /*
     This property enables the application to change orientation
     when the device is rotated. The default is false.
    */
    //automaticOrientation: true

    // Removes the old toolbar and enables new features of the new header.
    useDeprecatedToolbar: false

    width: units.gu(50)
    height: units.gu(75)

    Action {
        id: reloadAction
        text: "Reload"
        iconName: "reload"
        onTriggered: {
            console.log("reload is clicked")
        }
    }

    Tabs {
        id: tabs
        Tab {
            title: i18n.tr("Simple page")
            page: Page {
                Label {
                    id: label
                    anchors.centerIn: parent
                    text: "A centered label"
                }
                tools: ToolbarItems {
                    ToolbarButton {
                        action: reloadAction
                    }
                }
            }
        }

        Tab {
            id: externalTab
            title: i18n.tr("External")

            page: Loader {
                id: loader
                anchors.fill: parent
                source: (tabs.selectedTab === externalTab) ? Qt.resolvedUrl("ExternalPage.qml") : ""

                onLoaded: {
                    console.log( loader.source + " is loaded")
                }
            }
        }

        Tab {
            title: i18n.tr("List view")
            page: Page {
                ListView {
                    clip: true
                    anchors.fill: parent
                    model: 20
                    delegate: ListItem.Standard {
                        iconName: "compose"
                        text: "Item "+modelData
                    }
                }
            }
        }
    }
}

运行我们的应用:


   

所有的源码在:

https://gitcafe.com/ubuntu/TabApp4.git

我们如果想在Tab架构中使用pagestack的话,我们对我们的应用必须做一些修改。我们只能把Tabs作为第一个页面推到PageStack的栈中。Main.qml具体实现如下:

import QtQuick 2.0
import Ubuntu.Components 1.1
import Ubuntu.Components.ListItems 1.0 as ListItem

MainView {
    // objectName for functional testing purposes (autopilot-qt5)
    objectName: "mainView"

    // Note! applicationName needs to match the "name" field of the click manifest
    applicationName: "tabapp.ubuntu"

    /*
     This property enables the application to change orientation
     when the device is rotated. The default is false.
    */
    //automaticOrientation: true

    // Removes the old toolbar and enables new features of the new header.
    useDeprecatedToolbar: false

    width: units.gu(50)
    height: units.gu(75)

    Action {
        id: reloadAction
        text: "Reload"
        iconName: "reload"
        onTriggered: {
            console.log("reload is clicked")
        }
    }

    PageStack {
        id: pageStack
        Component.onCompleted: push(tabs)

        Tabs {
            id: tabs
            Tab {
                title: "Tab 1"
                page: Page {
                    Button {
                        anchors.centerIn: parent
                        onClicked: pageStack.push(page3)
                        text: "Press"
                    }
                }
            }
            Tab {
                title: "Tab 2"
                page: Page {
                    Label {
                        anchors.centerIn: parent
                        text: "Use header to navigate between tabs"
                    }
                }
            }
        }
        Page {
            id: page3
            visible: false
            title: "Page on stack"
            Label {
                anchors.centerIn: parent
                text: "Press back to return to the tabs"
            }
        }
    }
}


运行我们的应用,我们可以看到:

   

我们可以看见在上面显示的那样,有一个叫做“Page on stack”。可以通过按下换回箭头回到上一个页面。

具体的代码:

git clone https://gitcafe.com/ubuntu/TabApp3.git

2)使用PageStack来导航


在这一节中,我们将介绍如何使用PageStack来管理我们的页面。当用户进入下一个页面完成自己的工作后,可以通过按下标题栏中的返回箭头回到上一个页面。按照上面同样的步骤,我们可以创建一个叫做PageStack的项目。Main.qml的设计如下:

import QtQuick 2.0
import Ubuntu.Components 1.1
import Ubuntu.Components.ListItems 1.0 as ListItem

/*!
    \brief MainView with a Label and Button elements.
*/

MainView {
    // objectName for functional testing purposes (autopilot-qt5)
    objectName: "mainView"

    // Note! applicationName needs to match the "name" field of the click manifest
    applicationName: "pagestack.ubuntu"

    /*
     This property enables the application to change orientation
     when the device is rotated. The default is false.
    */
    //automaticOrientation: true

    // Removes the old toolbar and enables new features of the new header.
    useDeprecatedToolbar: false

    width: units.gu(50)
    height: units.gu(75)

    PageStack {
        id: pageStack
        Component.onCompleted: {
            push(page0)
        }

        Page {
            id: page0
            title: i18n.tr("Root page")
            visible: false

            Column {
                anchors.fill: parent

                ListItem.Standard {
                    text: i18n.tr("Page one")
                    onClicked: pageStack.push(page1, {color: UbuntuColors.orange})
                    progression: true
                }
                ListItem.Standard {
                    text: i18n.tr("Page two")
                    onClicked: pageStack.push(Qt.resolvedUrl("Page2.qml"))
                    progression: true
                }
            }
        }

        Page {
            title: "Rectangle"
            id: page1
            visible: false
            property alias color: rectangle.color
            Rectangle {
                id: rectangle
                anchors {
                    fill: parent
                    margins: units.gu(5)
                }
            }
        }
    }
}


这里我们在应用启动时创建一个PageStack,并同时把“page0”压入栈中。使它成为第一个页面。在“page0”中,我们有有两个列表项,分别可以进入到下一个页面中。

运行我们的应用:

   

我们可以在每个页面看见有个返回的箭头。

整个项目的源码在:

git clone https://gitcafe.com/ubuntu/PageStack.git




作者:UbuntuTouch 发表于2015/3/25 10:20:09 原文链接
阅读:557 评论:2 查看评论

Read more
UbuntuTouch

尽管我们的developer网站有丰富的API介绍,但是,有些API的介绍可能并不全,有些API也在不断地演进中。为了得到更详细的API,我们可以通过如下的命令来得到更加详细的信息。比如我们对“SingleDownload”API来得到更加多的信息。


$qmlplugindump Ubuntu.DownloadManager 0.1



显示的结果如下:


import QtQuick.tooling 1.1


// This file describes the plugin-supplied types contained in the library.
// It is used for QML tooling purposes only.
//
// This file was auto-generated by:
// 'qmlplugindump Ubuntu.DownloadManager 0.1'


Module {
    Component {
        name: "Ubuntu::DownloadManager::DownloadError"
        prototype: "QObject"
        exports: ["Error 0.1"]
        exportMetaObjectRevisions: [0]
        Property { name: "type"; type: "string"; isReadonly: true }
        Property { name: "message"; type: "string"; isReadonly: true }
    }
    Component {
        name: "Ubuntu::DownloadManager::SingleDownload"
        prototype: "QObject"
        exports: ["SingleDownload 0.1"]
        exportMetaObjectRevisions: [0]
        Property { name: "autoStart"; type: "bool" }
        Property { name: "errorMessage"; type: "string"; isReadonly: true }
        Property { name: "isCompleted"; type: "bool"; isReadonly: true }
        Property { name: "downloadInProgress"; type: "bool"; isReadonly: true }
        Property { name: "allowMobileDownload"; type: "bool" }
        Property { name: "throttle"; type: "qulonglong" }
        Property { name: "progress"; type: "int"; isReadonly: true }
        Property { name: "downloading"; type: "bool"; isReadonly: true }
        Property { name: "downloadId"; type: "string"; isReadonly: true }
        Property { name: "headers"; type: "QVariantMap" }
        Signal {
            name: "canceled"
            Parameter { name: "success"; type: "bool" }
        }
        Signal {
            name: "finished"
            Parameter { name: "path"; type: "string" }
        }
        Signal {
            name: "paused"
            Parameter { name: "success"; type: "bool" }
        }
        Signal {
            name: "processing"
            Parameter { name: "path"; type: "string" }
        }
        Signal {
            name: "progressReceived"
            Parameter { name: "received"; type: "qulonglong" }
            Parameter { name: "total"; type: "qulonglong" }
        }
        Signal {
            name: "resumed"
            Parameter { name: "success"; type: "bool" }
        }
        Signal {
            name: "started"
            Parameter { name: "success"; type: "bool" }
        }
        Signal {
            name: "errorFound"
            Parameter { name: "error"; type: "DownloadError&" }
        }
        Signal { name: "errorChanged" }
        Method {
            name: "registerError"
            Parameter { name: "error"; type: "Error"; isPointer: true }
        }
        Method {
            name: "bindDownload"
            Parameter { name: "download"; type: "Download"; isPointer: true }
        }
        Method {
            name: "unbindDownload"
            Parameter { name: "download"; type: "Download"; isPointer: true }
        }
        Method {
            name: "onFinished"
            Parameter { name: "path"; type: "string" }
        }
        Method {
            name: "onProgress"
            Parameter { name: "received"; type: "qulonglong" }
            Parameter { name: "total"; type: "qulonglong" }
        }
        Method {
            name: "onPaused"
            Parameter { name: "wasPaused"; type: "bool" }
        }
        Method {
            name: "onResumed"
            Parameter { name: "wasResumed"; type: "bool" }
        }
        Method {
            name: "onStarted"
            Parameter { name: "wasStarted"; type: "bool" }
        }
        Method {
            name: "onCanceled"
            Parameter { name: "wasCanceled"; type: "bool" }
        }
        Method { name: "start" }
        Method { name: "pause" }
        Method { name: "resume" }
        Method { name: "cancel" }
        Method {
            name: "download"
            Parameter { name: "url"; type: "string" }
        }
    }
    Component {
        name: "Ubuntu::DownloadManager::UbuntuDownloadManager"
        prototype: "QObject"
        exports: ["DownloadManager 0.1"]
        exportMetaObjectRevisions: [0]
        Property { name: "autoStart"; type: "bool" }
        Property { name: "cleanDownloads"; type: "bool" }
        Property { name: "errorMessage"; type: "string"; isReadonly: true }
        Property { name: "downloads"; type: "QVariantList"; isReadonly: true }
        Signal { name: "errorChanged" }
        Method {
            name: "download"
            Parameter { name: "url"; type: "string" }
        }
    }
}


我们通过使用“finished”信号可以得到下载完成的事件而进行分别的处理!


另外一个例子(Push notification)


liuxg@liuxg:~$ qmlplugindump Ubuntu.PushNotifications 0.1
import QtQuick.tooling 1.1

// This file describes the plugin-supplied types contained in the library.
// It is used for QML tooling purposes only.
//
// This file was auto-generated by:
// 'qmlplugindump Ubuntu.PushNotifications 0.1'

Module {
    Component {
        name: "PushClient"
        prototype: "QObject"
        exports: ["PushClient 0.1"]
        exportMetaObjectRevisions: [0]
        Property { name: "appId"; type: "string" }
        Property { name: "token"; type: "string"; isReadonly: true }
        Property { name: "notifications"; type: "QStringList"; isReadonly: true }
        Property { name: "status"; type: "string"; isReadonly: true }
        Property { name: "persistent"; type: "QStringList"; isReadonly: true }
        Property { name: "count"; type: "int" }
        Signal {
            name: "countChanged"
            Parameter { type: "int" }
        }
        Signal {
            name: "notificationsChanged"
            Parameter { type: "QStringList" }
        }
        Signal {
            name: "persistentChanged"
            Parameter { type: "QStringList" }
        }
        Signal {
            name: "appIdChanged"
            Parameter { type: "string" }
        }
        Signal {
            name: "error"
            Parameter { type: "string" }
        }
        Signal {
            name: "tokenChanged"
            Parameter { type: "string" }
        }
        Signal {
            name: "statusChanged"
            Parameter { type: "string" }
        }
        Method { name: "getNotifications" }
        Method {
            name: "notified"
            Parameter { name: "appId"; type: "string" }
        }
        Method { name: "emitError" }
        Method {
            name: "clearPersistent"
            Parameter { name: "tags"; type: "QStringList" }
        }
    }
}


作者:UbuntuTouch 发表于2015/3/12 14:55:15 原文链接
阅读:415 评论:5 查看评论

Read more
UbuntuTouch

在这篇文章中,我们将使用Ubuntu SDK所提供的online account API来访问微博的API并显示所需要的内容。这篇文章的重点是展示如何在HTML 5中使用online account API。我们将创建一个简单的HTML 5应用。我们最终显示的画面如下:


  


更多关于HTML5应用开发的资料可以在地址:https://developer.ubuntu.com/en/apps/html-5/


1)创建一个最基本的应用


我们还是像以前一样使用我们的Ubuntu SDK来创建一个最基本的weibo HTML 5应用。


  

  

这样我们就创建了一个最基本的weibo HTML 5应用。你可以使用热键Ctrl + R来运行它虽然它并不能做什么事。






2)加入online account所需要的文件



我们可以参考文章来对online account API有更深的了解。为了能够访问,需要创建如下的文件:


1)weibo.application

<?xml version="1.0" encoding="UTF-8"?>
<application>
  <description>Weibo scope</description>
  <desktop-entry>weibo.ubuntu_weibo.desktop</desktop-entry>
  <services>
    <service id="html5-weibo.ubuntu_html5weibo">
      <description>Watch your favorite Weibo messages</description>
    </service>
  </services>
</application>

2)weibo.service


<?xml version="1.0" encoding="UTF-8"?>
<service>
  <type>sharing</type>
  <name>Weibo scope</name>
  <icon>html5-weibo.png</icon>
  <provider>html5-weibo.ubuntu_account-plugin</provider>
  <translations>unity-scope-weibo</translations>
</service><strong>
</strong>

3)创建一个plugin文件目录并在它的下面创建

 a)Main.qml, 它的内容为:

import Ubuntu.OnlineAccounts.Plugin 1.0

OAuthMain {}

    b) qml-weibo.ubuntu_plugin.provider,它的内容为:

<?xml version="1.0" encoding="UTF-8"?>
<provider>
  <name>Weibo</name>
  <icon>weibo.png</icon>
  <translations>unity-scope-weibo</translations>
  <plugin>generic-oauth</plugin>
  <domains>.*weibo\.com</domains>
  <single-account>true</single-account>

  <template>
    <group name="auth">
      <setting name="method">oauth2</setting>
      <setting name="mechanism">web_server</setting>
      <group name="oauth2">
        <group name="web_server">
          <setting name="Host">api.weibo.com</setting>
          <setting name="AuthPath">oauth2/authorize</setting>
          <setting name="TokenPath">oauth2/access_token</setting>
          <setting name="RedirectUri">https://api.weibo.com/oauth2/default.html</setting>
          <setting name="ResponseType">code</setting>
          <setting name="ClientId">Your developer key</setting>
          <setting type="as" name="AllowedSchemes">['https','http']</setting>
          <setting name="ClientSecret">Your developer secret</setting>
          <setting name="ForceClientAuthViaRequestBody" type="b">true</setting>
      </group>
      </group>
    </group>
  </template>
</provider>


在上面的文件中一定要输入自己的“your developer key”及“your developer secret”。这个你需要在微博API的网站上去申请。整个文件的架构为:




4)修改manifest.json文件如下

{
    "name": "html5-weibo.ubuntu",
    "description": "description of html5-weibo",
    "architecture": "all",
    "title": "html5-weibo",
    "hooks": {
        "html5weibo": {
            "apparmor": "html5weibo.apparmor",
            "desktop": "html5weibo.desktop",
            "account-application": "weibo.application",
            "account-service": "weibo.service"
        },
        "account-plugin": {
            "account-provider": "plugin/html5-weibo.ubuntu_account-plugin.provider",
            "account-qml-plugin": "plugin/qml"
        }
    },
    "version": "0.1",
    "maintainer": "XiaoGuo, Liu <xiaoguo.liu@canonical.com>",
    "framework": "ubuntu-sdk-14.10"
}

这里添加了account及plugin的一些设置。


5)为了能够让.service,.application及.provider文件能正常得到显示,我们对“qml-weibo.qmlproject”添加如下的东西

    Files {
        filter: "*.service"
    }
    Files {
        filter: "*.application"
    }
    Files {
        filter: "*.provider"
    }


到这里,我们已经基本上已经修改或添加好我们所需要的文件。


3)设计应用的UI


修改我们的“index.html”文件:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>An Ubuntu HTML5 application</title>
    <meta name="description" content="An Ubuntu HTML5 application">
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0">

    <!--
         Application stylesheets
         Include your own stylesheets below to style your application.
      -->
    <link rel="stylesheet" href="css/app.css" type="text/css" />

    <!-- 
        Ubuntu UI javascript imports - Ambiance theme
        Ubuntu provides building blocks that you can use in your application. For more information, you can check out the documentation at http://design.ubuntu.com/apps.
     -->
    <!-- Ubuntu UI Style imports - Ambiance theme -->
    <link href="/usr/share/ubuntu-html5-ui-toolkit/0.1/ambiance/css/appTemplate.css" rel="stylesheet" type="text/css" />    
    <script src="/usr/share/ubuntu-html5-ui-toolkit/0.1/ambiance/js/fast-buttons.js"></script>
    <script src="/usr/share/ubuntu-html5-ui-toolkit/0.1/ambiance/js/core.js"></script>
    <script src="/usr/share/ubuntu-html5-ui-toolkit/0.1/ambiance/js/buttons.js"></script>
    <script src="/usr/share/ubuntu-html5-ui-toolkit/0.1/ambiance/js/page.js"></script>
    <script src="/usr/share/ubuntu-html5-ui-toolkit/0.1/ambiance/js/pagestacks.js"></script>
    <script src="/usr/share/ubuntu-html5-ui-toolkit/0.1/ambiance/js/popovers.js"></script>
    <script src="/usr/share/ubuntu-html5-ui-toolkit/0.1/ambiance/js/list.js"></script>
    <script src="/usr/share/ubuntu-html5-ui-toolkit/0.1/ambiance/js/toolbars.js"></script>
    <script src="/usr/share/ubuntu-html5-ui-toolkit/0.1/ambiance/js/tabs.js"></script>
    <script src="/usr/share/ubuntu-html5-ui-toolkit/0.1/ambiance/js/shape.js"></script>

</head>
<body>

    <div data-role="mainview">
      <header data-role="header">
      </header>

      <div data-role="content">
        <div data-role="pagestack">

          <div data-role="page" id="main" data-title="Weibo Status">

            <section data-role="list" id="statuslist"></section>

            <div id='results'></div>
          </div> <!-- main -->

        </div> <!-- pagestack -->
      </div> <!-- content -->
    </div> <!-- mainview -->

    <script src="js/app.js"></script>

  </body>
</html>

这里的UI非常简单,我只使用了一个list控件。我们大部分的代码将在“app.js”中实现:

var token = '';

window.onload = function () {
    console.log("this is so cool....!")

    var UI = new UbuntuUI();

    UI.init();
    UI.pagestack.push('main');
    var api = external.getUnityObject('1.0');
    var oa = api.OnlineAccounts;

    // UI.button('getstatus').click(auth);

    auth();

    function auth() {
        console.log("getstatus button is clicked!");

        var filters = {'provider': 'html5-weibo.ubuntu_account-plugin', 'service': ''};

        oa.api.getAccounts(filters, function(accounts) {
            console.log("total length: " + accounts.length);

            if (accounts.length < 1) {
                // setResults('No accounts available');
                oa.api.requestAccount(
                    "html5-weibo.ubuntu_html5weibo",
                    unescape("html5-weibo.ubuntu_account-plugin"),

                    function() {
                        console.log("requestAccount callback...");
                        auth();
                });
                return;
            } else {
                // setResults("Available accounts: " + accounts.length);
            }

            function authcallback(res) {
                token = res['data']['AccessToken'];
                console.log("AccessToken: " + token);
//                setResults("AccessToken: " + token);

                getWeiboStatus(token, function(statuses) {
                    console.log("the length: " + statuses.length);

                    if (statuses) {
                        // create the 'ul' element
                        var ul = document.createElement('ul');
//                        var results = document.getElementById('results');
                        var results = document.querySelector('#results');
                        var statuslist = document.querySelector('#statuslist');
                        var ul = document.createElement('ul');

                        for (var i = 0; i < statuses.length; i ++) {

                            var li = document.createElement('li');
                            ul.appendChild(li);

                            var aside = document.createElement('aside');
                            li.appendChild(aside);

                            var img = document.createElement('img');
                            img.setAttribute('src', statuses[i]['profile_image_url']);
                            img.setAttribute('width', "50");
                            img.setAttribute('height', "50");
                            aside.appendChild(img);

                            var a = document.createElement('a');
                            a.setAttribute('href', '#');
                            a.innerHTML = statuses[i]['text'];
                            li.appendChild(a);

                            var right = document.createElement('label');
                            right.innerHTML = ""
                            li.appendChild(right);

                        }

                        console.log("it is done6");
                        statuslist.appendChild(ul);
                    }
                    else {
                        setResults('<br><br>ERROR');
                    }
                });
            }

            accounts[0].authenticate(authcallback);
        }); //getAccounts
    } //auth

    function getWeiboStatus(accessToken, callback) {
        var http = new XMLHttpRequest()
        var url = "https://api.weibo.com/2/statuses/home_timeline.json?access_token=" + accessToken + "&page=" + 1

        console.log('url: \n' + url)

        http.open("GET", url, true);
        var statuses = [];
        http.onreadystatechange = function() {
            if (http.readyState === 4){
                if (http.status == 200) {
                    var response = JSON.parse(http.responseText);
                    console.log("it succeeds! lenght: " );

                    for (i = 0; i < response['statuses'].length; i++ ) {
                        var time = JSON.stringify(response['statuses'][i]['created_at']);
//                        console.log("time: " + time );

                        var text = JSON.stringify(response['statuses'][i]['text']);
//                        console.log("text: " + text);

                        var name = JSON.stringify(response['statuses'][i]['user']['name']);
//                        console.log("name: " + name);

                        var profile_image_url = JSON.stringify(response['statuses'][i]['user']['profile_image_url']);
                        profile_image_url = profile_image_url.slice(1, profile_image_url.length-1);
//                      console.log("profile_image_url: " + profile_image_url);

                        var id = JSON.stringify(response['statuses'][i]['idstr']);

                        statuses.push({'time': time,
                                       'text': text,
                                       'name': name,
                                       'id': id,
                                       'profile_image_url': profile_image_url});
                    }

                    callback(statuses);

                } else {
                    console.log("error: " + http.status)
                    callback(null);
                }
            }
        };
        http.send(null);
    }

    function setResults(data) {
        var results = document.getElementById('results');
        results.innerHTML = data;
    };

}; //onload

我们可以使用如下的句子来得到online account API的接口:

   var api = external.getUnityObject('1.0');
   var oa = api.OnlineAccounts;


在这里,我们使用HTML 5的online account API来检查是否有如下的provider:

var filters = {'provider': 'html5-weibo.ubuntu_account-plugin', 'service': ''};

如果没有,我们使用如下的代码来创建weibo账号:

           if (accounts.length < 1) {
                // setResults('No accounts available');
                oa.api.requestAccount(
                    "html5-weibo.ubuntu_html5weibo",
                    unescape("html5-weibo.ubuntu_account-plugin"),

                    function() {
                        console.log("requestAccount callback...");
                        auth();
                });
                return;
            }

等创建成功了,我们使用“auth()”来得到访问weibo的access token。进而我们可以得到我们所需要的任何的信息。

被创建的账号,可以在“系统设置”里的“账号”中可以看到:



另外我们可以在手机或模拟器中的如下的目录中看到我们所创建的文件:



整个项目的源码在:git clone https://gitcafe.com/ubuntu/html5weibo.git

作者:UbuntuTouch 发表于2015/3/16 10:07:57 原文链接
阅读:481 评论:0 查看评论

Read more
UbuntuTouch

在这篇文章中,我们来介绍如何使用在线(online)的Webapp生成器来生产在Ubuntu手机或模拟器中可以安装的click安装包。Webapp生成器的地址:https://developer.ubuntu.com/webapp-generator/


1)打开生成器网页,并填入相应的信息


我们在浏览器中打开网页:https://developer.ubuntu.com/webapp-generator/, 并填入应用所需要的信息。请注意:如果你还没有Ubuntu One账号,请注册一个,否则你将不能进入。Ubuntu One账号在上传应用到商店时也是必须有的账号。






等填完我们所需要的信息后,我们点击按钮“Submit”,就可以在屏幕的做下方出现下载的一个叫做类似“wqqcom-uii.liu-xiao-guo_0.1_all.click”的Click安装包。这个文件通常应该在:

/home/HOST_NAME/Downloads

文件夹中(这里“HOSE_NAME”)根据自己的电脑名字的不同而不同。

2)安装应用到我们的手机或模拟器中


我们通过如下的命令来把我们生产的click安装包安装到手机或模拟器中。注意我们上面所生产的click包的名字中含有“all",说明它是对手机和模拟器都适用的安装包。我们打开一个Terminal,并在其中打入如下的命令:

liuxg@liuxg:~/Downloads$ adb push wqqcom-uii.liu-xiao-guo_0.1_all.click /home/phablet



紧接着,我们打入如下的命令:

liuxg@liuxg:~/Downloads$ adb shell
phablet@ubuntu-phablet:~$ pkcon --allow-untrusted install-local wqqcom-uii.liu-xiao-guo_0.1_all.click 





这样我们就把应用安装到手机或模拟器中。如果在手机桌面上没有找到该安装的应用,可以尝试搜索“qq”(对我们的这个例子而言,它的名字中含有qq)。

  


在模拟器中的安装和运行,显示如下:

     

欢迎大家生产自己喜欢的Webapps,并把它上传到我们的商店中。另外值得指出的是:我们也可以适用SDK中的“Web app”模版来生产我们喜欢的在线Webapps。




更多关于在线Webapp的开发的信息可以在地址https://developer.ubuntu.com/zh-cn/web/找到。

作者:UbuntuTouch 发表于2015/3/19 9:31:27 原文链接
阅读:458 评论:0 查看评论

Read more
UbuntuTouch

几天以前,有一个开发者问道如何得到TextArea中的slider的位置信息。目前在QML中的TextArea中并没有这个信息,那么我们如何得到这个信息呢?


1)通过遍历TextArea中的child来得到这个信息


我们可以通过研究TextArea的代码,我们可以发现其中是有一个叫做Flickable的child在里面的。它的里面的contentY含有这个信息,但是可惜的是它不暴露这个信息给我们,那么我们怎么才能得到这个信息呢?

我们设计了如下的测试程序:

import QtQuick 2.0
import Ubuntu.Components 1.1

/*!
    \brief MainView with a Label and Button elements.
*/

MainView {
    // objectName for functional testing purposes (autopilot-qt5)
    objectName: "mainView"

    // Note! applicationName needs to match the "name" field of the click manifest
    applicationName: "textareatest.ubuntu"

    /*
     This property enables the application to change orientation
     when the device is rotated. The default is false.
    */
    //automaticOrientation: true

    // Removes the old toolbar and enables new features of the new header.
    useDeprecatedToolbar: false

    width: 450
    height: 600

    Page {
        title: i18n.tr("Simple")
        clip:true

        Button {
            id: button
            anchors.top: parent.top
            anchors.left: parent.left
            anchors.right: parent.right
            anchors.bottom: textArea.top

            text: "Get contentY"

            onClicked: {
                console.log("it is clicked!");

                // Get hold all of the children of the textArea
                var count = textArea.children.length
                console.log("length: " + count);

                for(var i=0; i < count; i++) {
                    var contentY = textArea.children[i].contentY;

                    if ( contentY === undefined) {
                        console.log("there is no contentY");
                    } else {
                        console.log("contentY: " + contentY);
                    }
                }
            }
        }

        TextArea {
            id: textArea
            anchors.left: parent.left
            anchors.right: parent.right
            anchors.bottom: parent.bottom
            height: 450

            readOnly: true
            wrapMode: TextEdit.Wrap
            text: "this is cool!\n
                   this is cool!\n
                   this is cool!\n
                   this is cool!\n
                   this is cool!\n
                   this is cool!\n
                   this is cool!\n
                   this is cool!\n
                   this is cool!\n
                   this is cool!\n
                   this is cool!\n
                   this is cool!\n
                   this is cool!\n
                   this is cool!\n
                   this is cool!\n
                   this is cool!\n
                   this is cool!\n
                   this is cool!\n
                   this is cool!\n
                   this is cool!\n
                   this is cool!\n
                   this is cool!\n
                   this is cool!\n
                   this is cool!\n
                   this is cool!\n
                   this is cool!\n
                   this is cool!\n
                   this is cool!\n
                   this is cool!\n
                   this is cool!\n"

            onContentHeightChanged: {
                console.log("ContentHeight: " + contentHeight);
                console.log("height: " + height);
            }

//            onContentYChanged: {
//                console.log("contentY: " + contentY);
//            }
        }
    }
}

界面如下:



我们按下“Get contentY”按钮后,可以看到在“Application output”窗口中显示的contentY的值。我们其实使用了如下的句子来实现的:

       Button {
            id: button
            anchors.top: parent.top
            anchors.left: parent.left
            anchors.right: parent.right
            anchors.bottom: textArea.top

            text: "Get contentY"

            onClicked: {
                console.log("it is clicked!");

                // Get hold all of the children of the textArea
                var count = textArea.children.length
                console.log("length: " + count);

                for(var i=0; i < count; i++) {
                    var contentY = textArea.children[i].contentY;

                    if ( contentY === undefined) {
                        console.log("there is no contentY");
                    } else {
                        console.log("contentY: " + contentY);
                    }
                }
            }
        }

通过遍历textArea的所有的child,并尝试得到contentY的属性。我们尝试滚动TextArea里的内容,就可以得到不同的contentY的值了。

所有的代码在地址:git clone https://gitcafe.com/ubuntu/TextArea1.git


2)通过重写Ubuntu的TextArea来实现


这个方法可能会导致在不同的framework上不兼容。我们下载相应平台的TextArea,并创建自己的MyTextArea如下:

/*
 * Copyright 2012 Canonical Ltd.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; version 3.
 *
 * 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 Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

import QtQuick 2.0
import Ubuntu.Components 1.1
import Ubuntu.Components 1.1 as Ubuntu
import "mathUtils.js" as MathUtils

/*!
    \qmltype TextArea
    \inqmlmodule Ubuntu.Components 1.1
    \ingroup ubuntu
    \brief The TextArea item displays a block of editable, scrollable, formatted
    text.

    The TextArea supports fix-size and auto-expanding modes. In fix-size mode the
    content is scrolled when exceeds the boundaries and can be scrolled both horizontally
    and vertically, depending on the contentWidth and contentHeight set. The following
    example will scroll the editing area both horizontally and vertically:

    \qml
    TextArea {
        width: units.gu(20)
        height: units.gu(12)
        contentWidth: units.gu(30)
        contentHeight: units.gu(60)
    }
    \endqml

    The auto-expand mode is realized using two properties: autoSize and maximumLineCount.
    Setting autoSize will set implicitHeight to one line, and the height will follow
    the line count, meaning when lines are added the area will expand and when
    removed the area will shrink. The maximumLineCount specifies how much the
    editor should be expanded. If this value is set to 0, the area will always
    expand vertically to fit the content. When autoSize is set, the contentHeight
    property value is ignored, and the expansion only happens vertically.

    \qml
    TextArea {
        width: units.gu(20)
        height: units.gu(12)
        contentWidth: units.gu(30)
        autoSize: true
        maximumLineCount: 0
    }
    \endqml

    TextArea comes with 30 grid-units implicit width and one line height on auto-sizing
    mode and 4 lines on fixed-mode. The line size is calculated from the font size and the
    ovarlay and frame spacing specified in the style.

    \section2 Scrolling and text selection
    The input is activated when the tap or mouse is released after being pressed
    over the component.

    Scrolling the editing area can happen when the size is fixed or in auto-sizing mode when
    the content size is bigger than the visible area. The scrolling is realized by swipe
    gestures, or by navigating the cursor.

    The content can be selected in the following ways:
    \list
    \li - double tapping/left mouse clicking over the content, when the word that
          had been tapped over will be selected
    \li - by pressing and dragging the selection cursor over the text input. Note
          that there has to be a delay of approx. 200 ms between the press and drag
          gesture, time when the input switches from scroll mode to selection mode
    \endlist

    The input is focused (activated) upon tap/left mouse button release. The cursor
    will be placed at the position the mouse/tap point at release time. If the click
    is happening on a selected area, the selection will be cleared. Long press above
    a selected area brings up the clipboard context menu. When the long press happens
    over a non-selected area, the cursor will be moved to the position and the component
    enters in selection mode. The selection mode can also be activated by tapping and
    keeping the tap/mouse over for approx 300 ms. If there is a move during this time,
    the component enters into scrolling mode. The mode is exited once the scrolling
    finishes. During the scrolling mode the selected text is preserved.

    \note During text selection all interactive parent Flickables are turned off.
  */

StyledItem {
    id: control
    implicitWidth: units.gu(30)
    implicitHeight: (autoSize) ? internal.minimumSize : internal.linesHeight(4)

    property alias contentY: flicker.contentY

    // new properties
    /*!
      The property presents whether the TextArea is highlighted or not. By
      default the TextArea gets highlighted when gets the focus, so can accept
      text input. This property allows to control the highlight separately from
      the focused behavior.
      */
    property bool highlighted: focus
    /*!
      Text that appears when there is no focus and no content in the component
      (hint text).

      \qmlproperty string placeholderText
      */
    property alias placeholderText: hint.text

    /*!
      This property contains the text that is displayed on the screen. May differ
      from the text property value when TextEdit.RichText format is selected.

      \qmlproperty string displayText
      */
    readonly property alias displayText: internal.displayText

    /*!
      The property drives whether text selection should happen with the mouse or
      not. The default value is true.
      \qmlproperty bool selectByMouse
      */
    property alias selectByMouse: editor.selectByMouse

    /*!
      \deprecated
      This property specifies whether the text area expands following the entered
      text or not. The default value is false.
      The property is deprecated, use autoSize instead
      */
    property bool autoExpand
    /*! \internal */
    onAutoExpandChanged: console.debug("WARNING: autoExpand deprecated, use autoSize instead.")

    /*!
      This property specifies whether the text area sizes following the line count
      or not. The default value is false.
      */
    property bool autoSize: false

    /*!
      The property holds the maximum amount of lines to expand when autoSize is
      enabled. The value of 0 does not put any upper limit and the control will
      expand forever.

      The default value is 5 lines.
      */
    property int maximumLineCount: 5

    // altered TextEdit properties
    /*!
      The property folds the width of the text editing content. This can be equal or
      bigger than the frame width minus the spacing between the frame and the input
      area defined in the current theme. The default value is the same as the visible
      input area's width.
      */
    property real contentWidth: control.width - 2 * internal.frameSpacing

    /*!
      The property folds the height of the text editing content. This can be equal or
      bigger than the frame height minus the spacing between the frame and the input
      area defined in the current theme. The default value is the same as the visible
      input area's height.
      */
    property real contentHeight: control.height - 2 * internal.frameSpacing

    /*!
      The property overrides the default popover of the TextArea. When set, the
      TextArea will open the given popover instead of the default one defined.
      The popover can either be a component or a URL to be loaded.
      */
    property var popover

    // forwarded properties
    /*!
      Whether the TextArea should gain active focus on a mouse press. By default this
      is set to true.
      \qmlproperty bool activeFocusOnPress
      */
    property alias activeFocusOnPress: editor.activeFocusOnPress

    /*!
      This property specifies a base URL which is used to resolve relative URLs within
      the text. The default value is the url of the QML file instantiating the TextArea
      item.

      \qmlproperty url baseUrl
      */
    property alias baseUrl: editor.baseUrl

    /*!
      Returns true if the TextArea is writable and the content of the clipboard is
      suitable for pasting into the TextArea.

      \qmlproperty bool canPaste
      */
    property alias canPaste: editor.canPaste

    /*!
      Returns true if the TextArea is writable and there are undone operations that
      can be redone.

      \qmlproperty bool canRedo
      */
    property alias canRedo: editor.canRedo

    /*!
      Returns true if the TextArea is writable and there are previous operations
      that can be undone.

      \qmlproperty bool canUndo
      */
    property alias canUndo: editor.canUndo

    /*!
      The text color.

      \qmlproperty color color
      */
    property alias color: editor.color

    /*!
      The delegate for the cursor in the TextArea.

      If you set a cursorDelegate for a TextArea, this delegate will be used for
      drawing the cursor instead of the standard cursor. An instance of the delegate
      will be created and managed by the text edit when a cursor is needed, and
      the x and y properties of delegate instance will be set so as to be one pixel
      before the top left of the current character.

      Note that the root item of the delegate component must be a QQuickItem or
      QQuickItem derived item.
      */
    property Component cursorDelegate: null

    /*!
      The position of the cursor in the TextArea.

      \qmlproperty int cursorPosition
      */
    property alias cursorPosition: editor.cursorPosition

    /*!
      The rectangle where the standard text cursor is rendered within the text
      edit. Read-only.

      The position and height of a custom cursorDelegate are updated to follow
      the cursorRectangle automatically when it changes. The width of the delegate
      is unaffected by changes in the cursor rectangle.

      \qmlproperty rectangle cursorRectangle
      */
    property alias cursorRectangle: editor.cursorRectangle

    /*!
      If true the text edit shows a cursor.

      This property is set and unset when the text edit gets active focus, but it
      can also be set directly (useful, for example, if a KeyProxy might forward
      keys to it).

      \qmlproperty bool cursorVisible
      */
    property alias cursorVisible: editor.cursorVisible

    /*!
      Presents the effective horizontal alignment that can be different from the one
      specified at horizontalAlignment due to layout mirroring.

      \qmlproperty enumeration effectiveHorizontalAlignment
      */
    property alias effectiveHorizontalAlignment: editor.effectiveHorizontalAlignment

    /*!
      The property holds the font used by the editing.

      \qmlproperty font font
      */
    property alias font: editor.font

    /*!
      Sets the horizontal alignment of the text within the TextAre item's width
      and height. By default, the text alignment follows the natural alignment
      of the text, for example text that is read from left to right will be
      aligned to the left.

      Valid values for effectiveHorizontalAlignment are:
        \list
        \li TextEdit.AlignLeft (default)
        \li TextEdit.AlignRight
        \li TextEdit.AlignHCenter
        \li TextEdit.AlignJustify
        \endlist

      \qmlproperty enumeration horizontalAlignment
      */
    property alias horizontalAlignment: editor.horizontalAlignment

    /*!
      This property holds whether the TextArea has partial text input from an
      input method.

      While it is composing an input method may rely on mouse or key events
      from the TextArea to edit or commit the partial text. This property can
      be used to determine when to disable events handlers that may interfere
      with the correct operation of an input method.

      \qmlproperty bool inputMethodComposing
      */
    property alias inputMethodComposing: editor.inputMethodComposing

    /*!
    Provides hints to the input method about the expected content of the text
    edit and how it should operate.

    The value is a bit-wise combination of flags or Qt.ImhNone if no hints are set.

    Flags that alter behaviour are:
    \list
    \li Qt.ImhHiddenText - Characters should be hidden, as is typically used when entering passwords.
    \li Qt.ImhSensitiveData - Typed text should not be stored by the active input method in any persistent storage like predictive user dictionary.
    \li Qt.ImhNoAutoUppercase - The input method should not try to automatically switch to upper case when a sentence ends.
    \li Qt.ImhPreferNumbers - Numbers are preferred (but not required).
    \li Qt.ImhPreferUppercase - Upper case letters are preferred (but not required).
    \li Qt.ImhPreferLowercase - Lower case letters are preferred (but not required).
    \li Qt.ImhNoPredictiveText - Do not use predictive text (i.e. dictionary lookup) while typing.
    \li Qt.ImhDate - The text editor functions as a date field.
    \li Qt.ImhTime - The text editor functions as a time field.
    \endlist
    Flags that restrict input (exclusive flags) are:

    \list
    \li Qt.ImhDigitsOnly - Only digits are allowed.
    \li Qt.ImhFormattedNumbersOnly - Only number input is allowed. This includes decimal point and minus sign.
    \li Qt.ImhUppercaseOnly - Only upper case letter input is allowed.
    \li Qt.ImhLowercaseOnly - Only lower case letter input is allowed.
    \li Qt.ImhDialableCharactersOnly - Only characters suitable for phone dialing are allowed.
    \li Qt.ImhEmailCharactersOnly - Only characters suitable for email addresses are allowed.
    \li Qt.ImhUrlCharactersOnly - Only characters suitable for URLs are allowed.
    \endlist
    Masks:

    \list
    \li Qt.ImhExclusiveInputMask - This mask yields nonzero if any of the exclusive flags are used.
    \endlist

      \qmlproperty enumeration inputMethodHints
      */
    property alias inputMethodHints: editor.inputMethodHints

    /*!
      Returns the total number of plain text characters in the TextArea item.

      As this number doesn't include any formatting markup it may not be the
      same as the length of the string returned by the text property.

      This property can be faster than querying the length the text property
      as it doesn't require any copying or conversion of the TextArea's internal
      string data.

      \qmlproperty int length
      */
    property alias length: editor.length

    /*!
      Returns the total number of lines in the TextArea item.

      \qmlproperty int lineCount
      */
    property alias lineCount: editor.lineCount

    /*!
      Specifies how text should be selected using a mouse.
        \list
        \li TextEdit.SelectCharacters - The selection is updated with individual characters. (Default)
        \li TextEdit.SelectWords - The selection is updated with whole words.
        \endlist
      This property only applies when selectByMouse is true.

      \qmlproperty enumeration mouseSelectionMode
      */
    property alias mouseSelectionMode: editor.mouseSelectionMode

    /*!
      Whether the TextArea should keep the selection visible when it loses active
      focus to another item in the scene. By default this is set to true;

      \qmlproperty enumeration persistentSelection
      */
    property alias persistentSelection: editor.persistentSelection

    /*!
      Whether the user can interact with the TextArea item. If this property is set
      to true the text cannot be edited by user interaction.

      By default this property is false.

      \qmlproperty bool readOnly
      */
    property alias readOnly: editor.readOnly

    /*!
      Override the default rendering type for this component.

      Supported render types are:
        \list
        \li Text.QtRendering - the default
        \li Text.NativeRendering
        \endlist
      Select Text.NativeRendering if you prefer text to look native on the target
      platform and do not require advanced features such as transformation of the
      text. Using such features in combination with the NativeRendering render type
      will lend poor and sometimes pixelated results.

      \qmlproperty enumeration renderType
      */
    property alias renderType: editor.renderType

    /*!
      This read-only property provides the text currently selected in the text edit.

      \qmlproperty string selectedText
      */
    property alias selectedText: editor.selectedText

    /*!
      The selected text color, used in selections.

      \qmlproperty color selectedTextColor
      */
    property alias selectedTextColor: editor.selectedTextColor

    /*!
      The text highlight color, used behind selections.

      \qmlproperty color selectionColor
      */
    property alias selectionColor: editor.selectionColor

    /*!
      The cursor position after the last character in the current selection.

      This property is read-only. To change the selection, use select(start, end),
      selectAll(), or selectWord().

      See also selectionStart, cursorPosition, and selectedText.

      \qmlproperty int selectionEnd
      */
    property alias selectionEnd: editor.selectionEnd

    /*!
      The cursor position before the first character in the current selection.

      This property is read-only. To change the selection, use select(start, end),
      selectAll(), or selectWord().

      See also selectionEnd, cursorPosition, and selectedText.

      \qmlproperty int selectionStart
      */
    property alias selectionStart: editor.selectionStart

    /*!
      The text to display. If the text format is AutoText the text edit will
      automatically determine whether the text should be treated as rich text.
      This determination is made using Qt::mightBeRichText().

      \qmlproperty string text
      */
    property alias text: editor.text

    /*!
      The way the text property should be displayed.
        \list
        \li TextEdit.AutoText
        \li TextEdit.PlainText
        \li TextEdit.RichText
        \endlist
      The default is TextEdit.PlainText. If the text format is TextEdit.AutoText
      the text edit will automatically determine whether the text should be treated
      as rich text. This determination is made using Qt::mightBeRichText().

      \qmlproperty enumeration textFormat
      */
    property alias textFormat: editor.textFormat

    /*!
      Sets the vertical alignment of the text within the TextAres item's width
      and height. By default, the text alignment follows the natural alignment
      of the text.

      Valid values for verticalAlignment are:

        \list
        \li TextEdit.AlignTop (default)
        \li TextEdit.AlignBottom
        \li TextEdit.AlignVCenter
        \endlist

        \qmlproperty enumeration verticalAlignment
      */
    property alias verticalAlignment: editor.verticalAlignment

    /*!
      Set this property to wrap the text to the TextEdit item's width. The text
      will only wrap if an explicit width has been set.
        \list
        \li TextEdit.NoWrap - no wrapping will be performed. If the text contains
            insufficient newlines, then implicitWidth will exceed a set width.
        \li TextEdit.WordWrap - wrapping is done on word boundaries only. If a word
            is too long, implicitWidth will exceed a set width.
        \li TextEdit.WrapAnywhere - wrapping is done at any point on a line, even
            if it occurs in the middle of a word.
        \li TextEdit.Wrap - if possible, wrapping occurs at a word boundary; otherwise
            it will occur at the appropriate point on the line, even in the middle of a word.
        \endlist
       The default is TextEdit.Wrap

       \qmlproperty enumeration wrapMode
      */
    property alias wrapMode:editor.wrapMode

    // signals
    /*!
      This handler is called when the user clicks on a link embedded in the text.
      The link must be in rich text or HTML format and the link string provides
      access to the particular link.
      */
    signal linkActivated(string link)

    // functions
    /*!
      Copies the currently selected text to the system clipboard.
      */
    function copy()
    {
        editor.copy();
    }

    /*!
      Moves the currently selected text to the system clipboard.
      */
    function cut()
    {
        editor.cut();
    }

    /*!
      Removes active text selection.
      */
    function deselect()
    {
        editor.deselect();
    }

    /*!
      Inserts text into the TextArea at position.
      */
    function insert(position, text)
    {
        editor.insert(position, text);
    }

    /*!
      Returns the text position closest to pixel position (x, y).

      Position 0 is before the first character, position 1 is after the first
      character but before the second, and so on until position text.length,
      which is after all characters.
      */
    function positionAt(x, y)
    {
        return editor.positionAt(x, y);
    }

    /*!
      Returns true if the natural reading direction of the editor text found
      between positions start and end is right to left.
      */
    function isRightToLeft(start, end)
    {
        return editor.isRightToLeft(start, end)
    }

    /*!
      Moves the cursor to position and updates the selection according to the
      optional mode parameter. (To only move the cursor, set the cursorPosition
      property.)

      When this method is called it additionally sets either the selectionStart
      or the selectionEnd (whichever was at the previous cursor position) to the
      specified position. This allows you to easily extend and contract the selected
      text range.

      The selection mode specifies whether the selection is updated on a per character
      or a per word basis. If not specified the selection mode will default to whatever
      is given in the mouseSelectionMode property.
      */
    function moveCursorSelection(position, mode)
    {
        if (mode === undefined)
            editor.moveCursorSelection(position, mouseSelectionMode);
        else
            editor.moveCursorSelection(position, mode);
    }

    /*!
      \preliminary
      Places the clipboard or the data given as parameter into the text input.
      The selected text will be replaces with the data.
    */
    function paste(data) {
        if ((data !== undefined) && (typeof data === "string") && !editor.readOnly) {
            var selTxt = editor.selectedText;
            var txt = editor.text;
            var pos = (selTxt !== "") ? txt.indexOf(selTxt) : editor.cursorPosition
            if (selTxt !== "") {
                editor.text = txt.substring(0, pos) + data + txt.substr(pos + selTxt.length);
            } else {
                editor.text = txt.substring(0, pos) + data + txt.substr(pos);
            }
            editor.cursorPosition = pos + data.length;
        } else
            editor.paste();
    }

    /*!
      Returns the rectangle at the given position in the text. The x, y, and height
      properties correspond to the cursor that would describe that position.
      */
    function positionToRectangle(position)
    {
        return editor.positionToRectangle(position);
    }

    /*!
      Redoes the last operation if redo is \l{canRedo}{available}.
      */
    function redo()
    {
        editor.redo();
    }

    /*!
      Causes the text from start to end to be selected.

      If either start or end is out of range, the selection is not changed.

      After calling this, selectionStart will become the lesser and selectionEnd
      will become the greater (regardless of the order passed to this method).

      See also selectionStart and selectionEnd.
      */
    function select(start, end)
    {
        editor.select(start, end);
    }

    /*!
      Causes all text to be selected.
      */
    function selectAll()
    {
        editor.selectAll();
    }

    /*!
      Causes the word closest to the current cursor position to be selected.
      */
    function selectWord()
    {
        editor.selectWord();
    }

    /*!
      Returns the section of text that is between the start and end positions.

      The returned text will be formatted according the textFormat property.
      */
    function getFormattedText(start, end)
    {
        return editor.getFormattedText(start, end);
    }

    /*!
      Returns the section of text that is between the start and end positions.

      The returned text does not include any rich text formatting. A getText(0, length)
      will result in the same value as displayText.
      */
    function getText(start, end)
    {
        return editor.getText(start, end);
    }

    /*!
      Removes the section of text that is between the start and end positions
      from the TextArea.
      */
    function remove(start, end)
    {
        return editor.remove(start, end);
    }

    /*!
      Undoes the last operation if undo is \l{canUndo}{available}. Deselects
      any current selection, and updates the selection start to the current
      cursor position.
      */
    function undo()
    {
        editor.undo();
    }

    // logic
    /*!\internal - to remove warnings */
    Component.onCompleted: {
        editor.linkActivated.connect(control.linkActivated);
    }

    // activation area on mouse click
    // the editor activates automatically when pressed in the editor control,
    // however that one can be slightly spaced to the main control area
    MouseArea {
        anchors.fill: parent
        enabled: internal.frameSpacing > 0
        acceptedButtons: Qt.LeftButton | Qt.RightButton
        // activate input when pressed on the frame
        preventStealing: false
        Ubuntu.Mouse.forwardTo: [inputHandler]
    }

    //internals

    opacity: enabled ? 1.0 : 0.3

    /*!\internal */
    onVisibleChanged: {
        if (!visible)
            control.focus = false;
    }

    LayoutMirroring.enabled: Qt.application.layoutDirection == Qt.RightToLeft
    LayoutMirroring.childrenInherit: true

    QtObject {
        id: internal
        // public property locals enabling aliasing
        property string displayText: editor.getText(0, editor.length)
        property real frameSpacing: control.__styleInstance.frameSpacing
        property real minimumSize: units.gu(4)

        function linesHeight(lines)
        {
            var lineHeight = editor.font.pixelSize * lines + inputHandler.lineSpacing * lines
            return lineHeight + 2 * frameSpacing;
        }

        function frameSize()
        {
            if (control.autoSize) {
                var max = (control.maximumLineCount <= 0) ?
                            control.lineCount :
                            Math.min(control.maximumLineCount, control.lineCount);
                control.height = linesHeight(MathUtils.clamp(control.lineCount, 1, max));
            }
        }
    }

    // grab Enter/Return keys which may be stolen from parent components of TextArea
    // due to forwarded keys from TextEdit
    Keys.onPressed: {
        if ((event.key === Qt.Key_Enter) || (event.key === Qt.Key_Return)) {
            if (editor.textFormat === TextEdit.RichText) {
                // FIXME: use control.paste("<br />") instead when paste() gets sich text support
                editor.insert(editor.cursorPosition, "<br />");
            } else {
                control.paste("\n");
            }
            event.accepted = true;
        } else {
            event.accepted = false;
        }
    }
    Keys.onReleased: event.accepted = (event.key === Qt.Key_Enter) || (event.key === Qt.Key_Return)

    // holding default values
    Label { id: fontHolder }

    //hint
    Label {
        id: hint
        anchors {
            fill: parent
            margins: internal.frameSpacing
        }
        // hint is shown till user types something in the field
        visible: (editor.getText(0, editor.length) == "") && !editor.inputMethodComposing
        color: Theme.palette.normal.backgroundText
        fontSize: "medium"
        elide: Text.ElideRight
        wrapMode: Text.WordWrap
    }

    //scrollbars and flickable
    Scrollbar {
        id: rightScrollbar
        flickableItem: flicker
    }
    Scrollbar {
        id: bottomScrollbar
        flickableItem: flicker
        align: Qt.AlignBottom
    }
    Flickable {
        id: flicker
        objectName: "input_scroller"
        anchors {
            fill: parent
            margins: internal.frameSpacing
        }
        clip: true
        contentWidth: editor.paintedWidth
        contentHeight: editor.paintedHeight
        // do not allow rebounding
        boundsBehavior: Flickable.StopAtBounds

        // editor
        // Images are not shown when text contains <img> tags
        // bug to watch: https://bugreports.qt-project.org/browse/QTBUG-27071
        TextEdit {
            objectName: "text_input"
            readOnly: false
            id: editor
            focus: true
            width: control.contentWidth
            height: Math.max(control.contentHeight, editor.contentHeight)
            wrapMode: TextEdit.WrapAtWordBoundaryOrAnywhere
            mouseSelectionMode: TextEdit.SelectWords
            selectByMouse: true
            activeFocusOnPress: true
            cursorDelegate: TextCursor {
                handler: inputHandler
            }
            color: control.__styleInstance.color
            selectedTextColor: Theme.palette.selected.foregroundText
            selectionColor: Theme.palette.selected.selection
            font.pixelSize: FontUtils.sizeToPixels("medium")
            // forward keys to the root element so it can be captured outside of it
            // as well as to InputHandler to handle PageUp/PageDown keys
            Keys.forwardTo: [control, inputHandler]

            // autosize handling
            onLineCountChanged: internal.frameSize()

            // input selection and navigation handling
            Ubuntu.Mouse.forwardTo: [inputHandler]
            InputHandler {
                id: inputHandler
                anchors.fill: parent
                main: control
                input: editor
                flickable: flicker
                frameDistance: Qt.point(flicker.x, flicker.y)
            }
        }
    }

    style: Theme.createStyleComponent("TextAreaStyle.qml", control)
}

这里我们加入了:

    property alias contentY: flicker.contentY

通过这样的方法,我们可以使得flicker的contentY的属性得以暴露,并被外面main.qml所使用:

import QtQuick 2.0
import Ubuntu.Components 1.1

/*!
    \brief MainView with a Label and Button elements.
*/

MainView {
    // objectName for functional testing purposes (autopilot-qt5)
    objectName: "mainView"

    // Note! applicationName needs to match the "name" field of the click manifest
    applicationName: "textareatest.ubuntu"

    /*
     This property enables the application to change orientation
     when the device is rotated. The default is false.
    */
    //automaticOrientation: true

    // Removes the old toolbar and enables new features of the new header.
    useDeprecatedToolbar: false

    width: 450
    height: 600

    Page {
        title: i18n.tr("Simple")
        clip:true

        Button {
            id: button
            anchors.top: parent.top
            anchors.left: parent.left
            anchors.right: parent.right
            anchors.bottom: textArea.top

            text: "Get contentY"

            onClicked: {
                console.log("it is clicked!");

                // Get hold all of the children of the textArea
                var count = textArea.children.length
                console.log("length: " + count);

                for(var i=0; i < count; i++) {
                    var contentY = textArea.children[i].contentY;

                    if ( contentY === undefined) {
                        console.log("there is no contentY");
                    } else {
                        console.log("contentY: " + contentY);
                    }
                }
            }
        }

        MyTextArea {
            id: textArea
            anchors.left: parent.left
            anchors.right: parent.right
            anchors.bottom: parent.bottom
            height: 450

            readOnly: true
            wrapMode: TextEdit.Wrap
            text: "this is cool!\n
                   this is cool!\n
                   this is cool!\n
                   this is cool!\n
                   this is cool!\n
                   this is cool!\n
                   this is cool!\n
                   this is cool!\n
                   this is cool!\n
                   this is cool!\n
                   this is cool!\n
                   this is cool!\n
                   this is cool!\n
                   this is cool!\n
                   this is cool!\n
                   this is cool!\n
                   this is cool!\n
                   this is cool!\n
                   this is cool!\n
                   this is cool!\n
                   this is cool!\n
                   this is cool!\n
                   this is cool!\n
                   this is cool!\n
                   this is cool!\n
                   this is cool!\n
                   this is cool!\n
                   this is cool!\n
                   this is cool!\n
                   this is cool!\n"

            onContentHeightChanged: {
                console.log("ContentHeight: " + contentHeight);
                console.log("height: " + height);
            }

            onContentYChanged: {
                console.log("contentY: " + contentY);
            }
        }
    }
}





当我们滚动时,就可以看见contentY的属性的变化。

这个工程的代码在: git clone https://gitcafe.com/ubuntu/TextArea2.git
作者:UbuntuTouch 发表于2015/4/13 9:26:54 原文链接
阅读:319 评论:0 查看评论

Read more